
我也没想到一个连接池能让我折腾一整天。几个月前接手一个老项目线上数据库偶发慢查询工程里用的还是Spring Boot默认的HikariCP。HikariCP的启动速度和极致性能确实没得挑可真到了要排查问题的时候它既没有直观的监控页面也没有内置的SQL分析能力只能靠人工写脚本去捞数据库状态。后来我把druid-spring-boot-starter引入工程整个排查链路才顺起来。这段时间的实践让我觉得关于Druid在Spring Boot项目里的正确用法真有必要系统性地写一写。这篇文章不打算做成官方文档的翻译而是按我实际接入的过程走一遍从为什么要换连接池开始到依赖引入、参数配置、监控面板接入、过滤器设置再到多数据源场景和一些实战中踩过的坑。内容面向正在用Spring Boot开发的同学尤其是生产环境里需要连接池监控、慢SQL排查和连接管理能力的团队。1. 为什么在Spring Boot项目里要专门引入Druid1.1 连接池之争HikariCP与Druid的选择逻辑Spring Boot 2.x默认把HikariCP作为数据源连接池这个选择在性能党眼里几乎无懈可击。HikariCP的字节码级优化让它成为目前最快的连接池之一很多Benchmark里它的吞吐量和延迟都遥遥领先。但快不代表够用尤其是当你的系统开始出现以下征兆时DBA问你要某个时段内SQL执行次数排名你拿不出来线上偶发连接池耗尽但事后查不到是谁泄漏了连接慢SQL要靠数据库端slow log才能捞出来无法和业务代码里的调用栈关联这些场景HikariCP就帮不上忙了。它本质上是一个纯粹的连接池只负责连接的分配、复用、健康检查不提供监控面板也不做SQL维度的统计。Druid就不一样。它是阿里开源的一个数据库连接池组件除了基础的池化能力还内置了监控、SQL防火墙、密码加密、Filter扩展链等一整套东西。而druid-spring-boot-starter正是官方为Spring Boot准备的自动装配starter引入后不需要徒手写一堆Configuration代码配置项直接写在application.yml里即可。1.2 这套方案解决了我哪些痛点我实际用下来以下几个特性最值钱内置监控面板页面能直接看到数据源活跃连接数、SQL执行耗时分布、慢SQL列表、访问Session数等排障时不用再到处翻日志。SQL防火墙可以拦截危险SQL比如不带WHERE条件的DELETE、UPDATE避免手滑造成灾难。密码加密生产环境数据库密码不用明文躺在配置文件里用Druid自带的ConfigTools生成密文即可。Filter扩展机制可以通过自定义Filter在SQL执行前后做埋点、日志输出、耗时统计扩展性很强。不过我也要说句公道话如果是一个小型项目、并发不高、没有明确监控需求HikariCP完全够用不必为了Druid大而全而盲目引入。它适合的是已经有线上运维压力、需要可观测性支撑的团队。2. 环境准备与依赖引入的关键细节2.1 版本匹配别让starter版本拖累你druid-spring-boot-starter的版本和Spring Boot主版本之间没有特别严格的绑定关系但根据我的测试Spring Boot 2.7.x配Druid 1.2.20是稳定组合如果你用的是Spring Boot 3.x建议直接上1.2.20或更高版本新版本对jakarta.*命名空间的支持更完整。Maven坐标如下dependency groupIdcom.alibaba/groupId artifactIddruid-spring-boot-starter/artifactId version1.2.20/version /dependency这里有一个关键点经常被忽略如果你是通过spring-boot-starter-jdbc或spring-boot-starter-data-jpa引入数据源相关能力的Spring Boot会自动带上HikariCP。现在你引入了Druid starter但工程里还存在HikariCP的依赖两者会形成竞争关系虽然Druid starter会尝试接管但最好显式排除掉HikariCP避免后续出现意料之外的行为dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-jdbc/artifactId exclusions exclusion groupIdcom.zaxxer/groupId artifactIdHikariCP/artifactId /exclusion /exclusions /dependency2.2 自动装配机制为什么不用手写DataSource很多老项目里的Druid用法还是几年前那套写一个Configuration类手动new DruidDataSource()再逐个setUrl、setUsername、setMaxActive。这套玩法在Spring Boot时代已经是过去式了。druid-spring-boot-starter底层依赖Spring Boot的ConditionalOnClass和EnableConfigurationProperties机制classpath下存在DruidDataSource、DruidStatViewServlet等核心类时starter自动创建数据源、统计管理器、Filter链并把application.yml里spring.datasource.druid.*前缀的配置绑定到DruidDataSource的属性上。所以正常情况下你只需要在配置文件中把参数写好Spring容器里就会有一个现成的DruidDataSource实例无需任何Java配置。这个机制理解透了后面排查配置没生效类问题时就能少走很多弯路。3. 数据源配置的完整实践从最小可用到生产调优3.1 十分钟跑通最精简配置长什么样最小化配置其实非常简单只需要spring.datasource下的常规几项外加druid前缀的参数块。我习惯的起步配置如下spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/demo?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 druid: initial-size: 5 min-idle: 5 max-active: 20 max-wait: 60000关键点有二。第一type一定要显式指定为DruidDataSource否则Spring Boot可能还是用classpath下优先级更高的HikariCP来做自动配置。第二spring.datasource.druid前缀下的参数命名推荐统一使用短横线风格initial-size而不是老文档里的驼峰式initialSize。starter对两种写法都有兼容处理但混合使用容易看晕固定一种风格对后续维护更友好。3.2 连接池参数调优什么样的配置才算合理生产环境绝不能停留在能跑阶段。下面这张表是我实测后觉得最需要关注的参数以及它的设置意图参数作用设定建议initial-size连接池启动时创建多少个连接建议与min-idle一致避免冷启动min-idle连接池里最少保留的空闲连接根据业务低峰期流量估算max-active连接池允许的最大连接数依据压测结果不能拍脑袋max-wait获取连接的最大等待毫秒数建议5000以内超时则快速报错time-between-eviction-runs-millis连接空闲检测周期默认60000即可min-evictable-idle-time-millis连接存活多久后可被回收默认300000注意不能小于检测周期test-while-idle空闲时是否执行validation-query推荐true提前发现坏连接test-on-borrow获取连接时是否执行验证默认false不要开降低性能损耗validation-query校验SQLMySQL用SELECT 1Oracle用SELECT 1 FROM DUAL还有一个很容易被忽略但极其重要的参数max-wait。我见过不少团队把这个参数设置为0意思是获取连接永不超时这在数据库故障时会直接导致线程无限期阻塞形成线程堆积。生产环境建议设置成3000或者5000宁可快速失败走降级逻辑也不要让上游请求全部卡死。3.3 连接池大小怎么定一个易懂的计算方式连接池的max-active不是越大越好。数据库能同时处理的连接数是有限的连接池开得过大后端数据库的连接数被占满反而拖垮数据库。我常用的估算方式是这样的假设业务峰值QPS为5000单次SQL平均耗时20ms那么单个数据库连接每秒最多处理50个请求。理论上需要的连接数是5000 / 50 100。但实际还要考虑连接获取竞争、网络抖动、非SQL事务占用等损耗一般在这个结果上乘以1.2到1.5的安全系数所以max-active大概定在120到150之间比较合理。同理min-idle可以按低峰期流量的百分比推算比如低峰QPS只有50对应连接数可能只需要3到5个设置成5就能覆盖绝大多数场景。如果你不知道怎么压测最简单的方法是先用max-active: 20上线观察一周通过Druid监控面板查看活跃连接数的峰值曲线再按这个峰值加30%余量来调整。这也是Druid监控面板最实用的场景之一。3.4 生产环境下数据库密码加密配置文件里明文写密码这在生产环境是绝对要避免的。Druid自带了一套对称加密机制用ConfigTools生成公钥、私钥和加密后的密码然后把私钥配置在JVM启动参数里或配置文件中只保留公钥和密文。生成密文的方式很简单命令行执行java -cp druid-1.2.20.jar com.alibaba.druid.filter.config.ConfigTools YourPassword123运行后会输出三个值privateKey、publicKey、password。其中password是加密后的密文publicKey用来做解密privateKey是初始生成用的仅保留备用即可。然后在配置文件中这样写spring: datasource: druid: filter: config: enabled: true connection-properties: config.decrypttrue;config.decrypt.key${DRUID_PUBLIC_KEY}connection-properties里的config.decrypttrue代表开启连接属性解密config.decrypt.key对应公钥。这里我用${DRUID_PUBLIC_KEY}的方式把公钥放在环境变量里避免公钥直接侵入配置文件。这个功能有个小坑如果项目里还同时设置了spring.datasource.password和druid.connection-properties中的config.decrypt解密完成后Druid会拿解密的密码去建立连接但部分旧版本在日志里会打印出明文密码。所以引入加密后一定要检查日志输出确保密码没有被泄露。4. 监控面板接入Druid的护城河4.1 StatViewServlet配置与访问控制接入Druid监控面板是整个方案最吸引人的地方。但注意druid-spring-boot-starter默认并不会自动开启监控页面必须通过配置项显式启用。spring: datasource: druid: stat-view-servlet: enabled: true url-pattern: /druid/* login-username: admin login-password: admin123 allow: 127.0.0.1 deny: 10.10.10.0/24 reset-enable: false参数含义逐一来说enabled是否开启监控Servlet不配这个你访问/druid一定会404。url-pattern监控页访问路径默认是/druid/*可以改成更隐蔽的路径。login-username和login-password监控页面登录凭证务必修改默认值。allow允许访问的IP白名单多个IP用逗号分隔不配置时所有IP都可以访问。deny黑名单优先级高于allow即deny中的IP永远无法访问。reset-enable是否允许在页面上点击重置清空统计数据生产环境调成false防止误操作把统计信息清掉。这里我一定要提醒有些开发者图方便把allow配成*且不设置登录密码监控页面对全网开放结果连接池里的SQL被别有用心的人整个看光。即便在内网环境也应该开启账号密码和IP白名单双重防护。4.2 监控页面各标签的核心指标解读监控页面打开后默认会有几个Tab数据源、SQL监控、SQL防火墙、URI监控、Session监控、Spring监控等。我刚接触时也有一阵子不知道看哪里现在沉淀下来重点关注这几个数据源Tab这个页面展示当前连接池的整体状态包括创建连接数、销毁连接数、活跃连接数、空闲连接数、获取连接等待次数等。我排查连接池用尽问题时首先看这里如果活跃连接数长期贴着max-active打满说明连接数量不够或存在连接泄漏。SQL监控Tab展示每一条执行过的SQL按执行次数、总耗时、最大耗时等维度排序。我一般把耗时排序打开直接看Top N。执行时间超过slow-sql-millis阈值的SQL会标记为慢SQL并记录执行时的参数列表。在这里能看到SQL的完整文本方便定位是哪一段业务代码发起的。SQL防火墙Tab开启防火墙功能后这里会展示命中拦截规则的SQL数量。比如一个DELETE没有WHERE条件直接被打回页面上会有记录。这个功能对资金类、核心业务系统特别有用。Session监控Tab结合WebStatFilter采集的数据可以看到哪个Session访问了什么URI、执行了什么SQL。排查某个用户触发了一次慢查询这种问题时非常高效。4.3 慢SQL记录与日志输出配置监控页面看的是历史数据平时我们还需要把慢SQL落到日志文件里方便告警和事后审计。配置方式如下spring: datasource: druid: filter: stat: enabled: true slow-sql-millis: 1000 log-slow-sql: trueslow-sql-millis表示SQL执行时间超过多少毫秒即视为慢SQL这里我设为1000ms即1秒。log-slow-sql设为true后命中的SQL会通过Druid的SLF4J Logger输出到日志中。有一点容易被忽略slow-sql-millis的阈值不要设太小。如果设成100ms开发环境很多正常查询都会触发慢日志过几天你就对日志里的慢SQL免疫了。我是根据业务接口的P99延迟来反推比如P99是500ms那么SQL阈值可以设800ms或1000ms保留一点弹性空间。除了慢SQL日志stat过滤器还支持connection-stack-trace-enable等参数用于在连接泄漏时打印获取连接的调用栈。这个功能排查连接池耗尽时堪称神器但会带来额外的性能开销建议只在定位问题时临时开启。5. 过滤器配置与Web应用监控把SQL和用户请求关联起来5.1 WebStatFilter采集Web层与JDBC层的关联数据只开启StatViewServlet你看到的SQL监控是孤立的它不知道这条SQL是哪个HTTP请求触发的。要让SQL和URI、Session关联起来就得配置WebStatFilterspring: datasource: druid: web-stat-filter: enabled: true url-pattern: /* exclusions: /druid/*,*.js,*.css,*.gif,*.jpg,*.png,*.ico session-stat-enable: trueurl-pattern设为/*表示拦截所有请求exclusions配置的是不参与统计的静态资源路径避免把CSS、JS这些请求也计入URI监控导致统计数值虚高。session-stat-enable开启后监控面板里就能看到Session维度的信息。有个细节我想重点提一下exclusions里的路径分隔符是英文逗号且每个路径要写完整。我之前把exclusions写成/druid/*,*.js看上去没问题但实际运行时发现*.js并没有起作用原因是Druid的匹配逻辑需要/*.js这种写法。后来改成/*.js静态资源才真正被排除掉。5.2 Spring监控与URI监控的配合如果你在配置文件里再开启Spring监控spring: datasource: druid: aop-patterns: com.example.demo.service.*Druid会通过AOP方式拦截到Service方法的调用并把它和SQL执行关联起来。监控面板的Spring监控Tab里就能看到每个Service方法调用了哪些SQL、耗时多少。这对于从慢SQL反推业务代码路径非常有帮助。aop-patterns的配置方式是Spring AOP的切点表达式我这里写的是匹配com.example.demo.service包下所有类的所有方法。需要说明的是这个功能依赖Spring AOP能力如果项目已经用了AOP要留意拦截器顺序问题避免多个切面互相影响。5.3 和Shiro或Spring Security的过滤器链冲突如果你项目里还有Shiro或Spring Security这种Web安全框架Druid的WebStatFilter默认是按照过滤器顺序注册在链路中的但安全过滤器通常优先级更高可能会在Druid过滤器之前直接拦截请求比如未登录用户的重定向导致部分请求没有被Druid统计到。我在实际项目里遇到的情况是Shiro的授权拦截器把/druid/*拦住了监控页面怎么都打不开。排查到最后才发现不是StatViewServlet没生效而是请求根本没走到Servlet层。解决方式是在安全框架的配置里把/druid/*加入白名单同时依靠StatViewServlet自己的login-username和login-password来做认证不要依赖安全框架的登录态。6. 实测中容易踩的坑与完整排查链路6.1 监控页面404是Servlet没启用还是被拦截了这个坑我猜90%的人都踩过。现象是浏览器访问http://localhost:8080/druid/index.html直接404但项目启动很正常数据库访问也没问题。排查链路分三步走查看StatViewServlet是否真正注册。可以在Spring Boot启动日志里搜索druid关键字正常能看到StatViewServlet的相关初始化信息。确认访问路径是否为/druid/index.html而不是/druid。Druid的监控首页重定向规则在不同版本里略有差异直接访问/druid/index.html最稳妥。查过滤器链路。就是上面说的Shiro/Spring Security拦截这一步最隐蔽日志里不会报错只能通过调试或临时关闭安全配置来验证。还有一次我被人问到为什么生产环境监控页面时好时坏最后发现是部署架构里有两个网关层一个Nginx把/druid/路径直接代理到了后端的某个服务实例上而那个实例没有开启监控其他实例是好的。这种负载均衡层面的问题排查起来最容易原地打转。所以如果你在集群环境里要先确认访问的是哪台实例再动手查Servlet。6.2 连接池参数没生效为什么配了max-active还是20有次我帮同事排查他明明在application.yml里配了spring.datasource.druid.max-active: 50但监控页面上显示max-active还是默认值20。这属于典型的配置前缀写错问题。他那份配置是这样写的spring: datasource: max-active: 50注意这里max-active直接挂在了spring.datasource下面而不是spring.datasource.druid下面。对于Druid starter来说它扫描的是spring.datasource.druid.*前缀因此顶层这个max-active根本不会被读取。Spring Boot原生数据源配置里这个属性也不是标准项因此就静默忽略了。排查这个问题的办法有三个用/actuator/env端点查看datasource相关的ConfigurationProperties绑定情况在代码里临时输出DruidDataSource实例的getMaxActive()值启动日志里搜索DruidDataSource初始化信息看打印的连接池参数另外特别提醒配置缩进和引号不要用错。application.yml是YAML格式缩进多一层少一层参数就会被解析到完全不同的位置比Java代码的显式赋值要隐蔽得多。6.3 启动时报错failed to initialize connection出现这个报错时Druid会在启动阶段尝试创建initial-size个连接。常见原因有三个原因一initial-size过大而数据库当前连接数已达上限。这种情况一般后端的MySQL会报too many connections错误。解决方案是调小initial-size和min-idle或者检查是否存在其他应用把数据库连接占满。我看到过因为多个微服务共享同一个数据库实例每个服务都配置了50个连接加起来几百个连接直接把MySQL打爆的案例。原因二validation-query写错或不适用。我在有Oracle和MySQL混合环境里遇到过建连校验SQL在MySQL下是SELECT 1到了Oracle就变成SELECT 1 FROM DUAL。如果配置共用必然有一部分实例启动失败。原因三Druid防火墙拦截了自己的IP。开启防火墙并配置deny规则后如果规则写得太激进启动时需要连接的常规IP被误拦一样会报初始化失败。这会表现为日志里有SecurityException类似的关键词。解决方案是先在deny里放行本机IP验证连接成功后再逐步放宽。6.4 连接池连接数暴涨连接泄漏排查思路这是生产环境最需要重视的问题之一。现象是监控面板上活跃连接数曲线一路上升即使没有业务请求也不回落。这通常意味着代码里有连接泄漏——某个连接被取走后没有正常归还。排查思路按顺序来开启test-while-idle让Druid的空闲连接检测能发现物理上已断开的连接。在stat过滤器里临时开启connection-stack-trace-enabletrue这样当连接无法归还时会打印获取它的调用栈。检查代码里是否用了DataSourceUtils.getConnection()而不是JdbcTemplate/MyBatis来管理连接如果手动getConnection()却没有在finally块里close()连接就会漏掉。检查事务配置是否异常比如Transactional作用在非公共方法上导致事务不生效或者自己实现的多数据源路由切错了DataSource导致连接在切换过程中被覆盖。Druid的连接池页面还有一个好处它能显示当前活动连接中哪些执行了超过time-between-eviction-runs-millis还未归还的SQL。定位SQL文本之后再按图索骥去找对应的Service方法比大海捞针有效率得多。6.5 监控数据全部为零WebStatFilter与StatViewServlet配置缺失还有一次有朋友反馈监控页面能打开但SQL监控记录始终为零。我远程一看他的配置里只开了stat-view-servlet.enabled没有开web-stat-filter也没有在spring.datasource.druid.filter.stat.enabled上做任何设置。原因在于SQL监控的数据采集是由stat过滤器完成的WebStatFilter负责把Web请求上下文和SqlSession关联起来。两者缺一不可。只开StatViewServlet相当于给你一个空面板里面没有任何数据来源。所以记得检查一下你的配置里是不是少了这块spring: datasource: druid: filter: stat: enabled: truefilter.stat.enabled是否默认开启不同Druid版本行为不太一样。老版本默认开启新版本为了不做字节码增强反而要求显式声明。这也是为什么照着网上的老配置抄了不能用的原因之一。7. 进阶场景多数据源下的Druid配置思路7.1 starter的多数据源限制druid-spring-boot-starter的自动装配能力在单数据源场景下足够好用一旦涉及多数据源就捉襟见肘因为自动装配只会创建一个DruidDataSource实例当你需要连接两个库时就必须自己动手写Configuration来定义多个数据源Bean。这也是网上资料最容易讲乱的地方。有人说Druid不能多数据源其实不是不能而是starter不帮你做需要回归Spring Boot的标准做法。7.2 双数据源配置示例假设我们有主库primary和从库secondary在application.yml里这样配置spring: datasource: primary: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://primary-host:3306/db1 username: root password: 123456 druid: initial-size: 5 max-active: 20 secondary: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://secondary-host:3306/db2 username: root password: 123456 druid: initial-size: 3 max-active: 10注意此时不能再用spring.datasource.url、spring.datasource.username这类主属性了否则会和primary、secondary的配置冲突导致数据源初始化顺序混乱。多数据源场景下每一组配置都是平级子节点。接着在Java配置类里创建两个数据源Configuration public class DataSourceConfig { Bean Primary ConfigurationProperties(prefix spring.datasource.primary) public DataSource primaryDataSource() { return DruidDataSourceBuilder.create().build(); } Bean ConfigurationProperties(prefix spring.datasource.secondary) public DataSource secondaryDataSource() { return DruidDataSourceBuilder.create().build(); } }这里有一个非常关键的细节必须用DruidDataSourceBuilder.create().build()创建数据源不能直接new DruidDataSource()。原因是DruidDataSourceBuilder能识别spring.datasource.primary.druid前缀下的参数并把它们正确绑定到DruidDataSource的属性上。如果用new DruidDataSource()配合ConfigurationPropertiesdruid子前缀的嵌套绑定会失效连接池参数全部回到默认值。至于MyBatis、JdbcTemplate如何切换数据源核心思路是给SqlSessionFactory和TransactionManager指定对应的数据源BeanBean Primary public SqlSessionFactory primarySqlSessionFactory( Qualifier(primaryDataSource) DataSource dataSource) throws Exception { SqlSessionFactoryBean factory new SqlSessionFactoryBean(); factory.setDataSource(dataSource); // 其他配置 return factory.getObject(); }这一段是多数据源场景下比较容易写错的地方如果不加PrimarySpring在注入DataSource的地方会因存在多个Bean而报NoUniqueBeanDefinitionException。7.3 从AbstractRoutingDataSource到动态数据源如果业务场景不只是主库从库两个固定库而是需要在运行时根据业务标识动态切换不同的数据库建议在前面多数据源的基础上再包一层AbstractRoutingDataSource。核心思路是在进入Service方法时往ThreadLocal里存入一个路由KeyAbstractRoutingDataSource的determineCurrentLookupKey()方法从ThreadLocal取出Key再决定走哪个数据源。我最早做这个功能时也走了弯路直接把所有DataSource塞进路由Map就算了结果忽略了事务失效问题。后来发现Transactional开启事务后连接是在事务开始时获取并绑定的如果事务已经开始再切换数据源后端连接根本不会切过去。所以动态数据源方案一定要配合自定义TransactionManager或者在事务开启之前就确定好路由Key。这里我不过度展开毕竟这是另一个大话题。但如果你确实面临多库动态路由需求强烈建议先跑通单库Druid再谈路由。最后再分享一点我个人实际操作的体会接入Druid这大半年我最大的感受是连接池的参数没有银弹监控数据是调优的唯一依据。刚开始我也按网上的推荐配置照抄max-active直接设个50结果监控面板一开发现业务低峰期只有3个活跃连接瞬间觉得自己很傻。后来就坚持一个原则——先上线看指标再调整。还有一个小技巧可以分享如果你用的是druid-spring-boot-starter尽量保持Druid版本在一个较新的稳定版本上。老版本在Spring Boot配置绑定和Filter默认开关上行为差异很大网上很多旧帖子已经失效了遇到问题优先去GitHub的Release Notes里查当前版本的变更记录比漫无目的地搜博客高效得多。再补一个日常用得上的习惯每周定期登录一次Druid监控页面看一眼SQL监控Tab里执行时间最长的五条SQL有没有异常变化。不要等线上报警了才去看那时候它只是帮你印证了一个事故而没能帮你避免事故。这可能是Druid对我而言最有价值的使用方式。