
新项目初始化时Spring Boot 默认的数据源是 HikariCP快是真快但真到线上排查慢 SQL、观察连接池水位的时候你会在监控面板上发现一片空白。如果你也遇到过“DBA 问慢 SQL 是哪条你只能靠日志一把梭”的场面那这篇文章应该是你的刚需。我在这几年维护 Spring Boot 项目的过程中把连接池从 HikariCP 切到了 druid-spring-boot-starter整个过程踩了不少坑也攒下一套可直接复制的配置。这篇文章适合手上已经有 Spring Boot 基础、想给项目加上连接池监控、慢SQL 统计和防误报能力的同学哪怕你现在只会写 CRUD照着下面的步骤走完一遍也能在十分钟内把 Druid 跑起来。1. 为什么选 druid-spring-boot-starter而不是自己写配置先说说选型问题。HikariCP 是 Spring Boot 2 之后的默认数据源性能数据很漂亮优点是极轻缺点是除了连接池基本功能外几乎不留什么观测入口。Druid 在并发性能上和 HikariCP 有差距但这个差距远没有很多人想得那么大。大部分业务系统瓶颈在 SQL、网络和业务逻辑而不是连接池获取连接本身。Druid 的优势是它自带一套全套的统计体系包括 SQL 维度的执行次数、返回行数、总耗时、最大耗时连接维度的活跃数、等待数甚至慢SQL 明细、并发度、防 SQL 注入过滤器。所以选 Druid 其实不是为了性能是为了能看见。代码层面看不见的东西很容易出事故。1.1 三大连接池的实际体验对比把 HikariCP、Druid、C3P0 放在一起对比思路会更清楚。HikariCP 是 Spring Boot 默认集成成本为零性能好字节码优化做得很足但它提供的监控能力非常单薄没有 SQL 维度的统计也没有可以扩展的 Filter 链。C3P0 是老牌连接池配置繁琐更新节奏慢在 Spring Boot 生态里基本已经边缘化。Druid 属于功能流连接池该有的能力它都有还额外提供了 stat filter、log4j filter、wall filter 这样一个可插拔的过滤器链。其中 wall filter 可以做 SQL 白名单和防注入stat filter 负责统计扩展起来比 HikariCP 舒服得多。在实际项目里怎么选如果项目规模很小、日志全、有专门的 APM 系统你用 HikariCP 完全没问题。但如果团队没有 APM或者 DBA 需要 SQL 级别耗时分布那我建议直接上 Druid。我见过不少团队一开始为了省事留在 HikariCP等线上出现连接池耗尽时才意识到连主动查看每个连接获取了多久、哪条 SQL 占用了连接都做不到。1.2 starter 到底替你做了哪三件事以前用 Druid 的老手习惯自己写一个 DataSourceConfig手动 new 一个DruidDataSource再配置StatViewServlet和WebStatFilter最后注册到容器里。这种写法不是不行但每次都要复制一份配置类还容易漏掉初始化逻辑。druid-spring-boot-starter的价值在于三件事第一根据spring.datasource.druid前缀自动绑定配置并创建DruidDataSource对象第二在配置stat-view-servlet.enabledtrue时自动帮你注册监控页用的StatViewServlet第三在配置web-stat-filter.enabledtrue时自动注册用于采集 Web 请求和 SQL 关联数据的WebStatFilter。用好这个 starter项目里几乎不需要额外写注册类pom 里加一个依赖application.yml 里写配置启动就能生效。有一点要特别提醒Spring Boot 3 升级时注意版本。druid-spring-boot-starter在 1.2.18 之前的版本对javax.servlet有强依赖而 Spring Boot 3 换成了jakarta.servlet版本太低会直接出现 ClassNotFoundException。建议直接选用 1.2.20 之后的版本兼容性更好。2. 核心参数连接池的每一格水位线很多人习惯只配置 max-active然后就不管其它参数了直到线上连接池被打满才回来看监控。其实 Druid 连接池的参数不是随便填的它们是一整套水位线机制理解清楚更容易定位问题。2.1 一条连接从生到死经历了什么连接池里维护的底层连接本质上是一次真实的数据库 TCP 连接。正常情况下从初始化到销毁要经历这几个阶段初始化阶段连接池会预先创建initial-size条连接放在池里业务线程调用getConnection()时从池里分配一条空闲连接状态从 idle 变为 active事务或查询结束后close()连接回到池中状态重新变为 idle后台的守护线程每隔time-between-eviction-runs-millis跑一次检查池中连接的空闲时长超过min-evictable-idle-time-millis的会被销毁低于min-idle时再新建补齐。这个过程有点像酒店前台管理房卡客人退房后房卡不会马上作废保洁检查一遍没问题才会重新放回柜台如果空置太久房卡会注销但前台会保证最少有几张可用房卡不至于客人一来就全员去办新卡。搞懂这个模型几个参数的默认意义就清楚了。max-active不是越大越好它决定了池子最多持有多少物理连接给多了数据库压力大给少了业务高峰期排队。max-wait决定了获取连接时最多等多久超过这个时间直接在业务线程抛出异常避免线程被卡死。test-while-idle配合validation-query让后台线程在把连接分配出去之前先做一次探测把坏连接提前踢掉避免业务请求遇到失效连接。2.2 一份可以直接抄的生产配置模板下面是我在生产项目里常用的一套 Druid 配置。注意不同业务的数据库压力不一样参数要基于压测调整但起步用这套基本不会踩明显的大坑。每一项都不是拍脑袋定的后面解释几个关键选择的理由。参数推荐值作用initial-size5启动时预创建连接数min-idle5池内最小空闲连接数max-active20最大活跃连接数max-wait60000获取连接最大等待时间单位毫秒time-between-eviction-runs-millis60000守护线程扫描间隔min-evictable-idle-time-millis300000空闲连接的存活阈值validation-querySELECT 1连接有效性探测 SQLtest-while-idletrue空闲时做有效性探测test-on-borrowfalse取连接时不探测依赖后台守护test-on-returnfalse归还连接时不探测pool-prepared-statementstrue开启 PS 缓存减少 SQL 解析max-pool-prepared-statement-per-connection-size20每个连接的 PS 缓存上限max-wait60000取连接超过 60 秒会抛出GetConnectionTimeoutException业务线程不会无限等下去。test-on-borrowfalse是性能考虑如果每次取连接都执行SELECT 1在高并发下反而给数据库增加一堆无意义的小查询所以让后台线程用test-while-idle兜底。pool-prepared-statementstrue对使用预编译 SQL 较多的系统有明显效果能减少 Statement 在数据库端的重复解析代价是连接占用的堆内存会多一点。配置文件中对应的写法如下spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/demo?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: your_password druid: initial-size: 5 min-idle: 5 max-active: 20 max-wait: 60000 time-between-eviction-runs-millis: 60000 min-evictable-idle-time-millis: 300000 validation-query: SELECT 1 test-while-idle: true test-on-borrow: false test-on-return: false pool-prepared-statements: true max-pool-prepared-statement-per-connection-size: 20如果你用的是 MySQL 8 的驱动driver-class-name 必须写成com.mysql.cj.jdbc.Driver时区参数serverTimezone也尽量显式指定否则客户端和数据库时区不一致时日期字段查询结果可能莫名其妙错掉。2.3 removeAbandoned 参数要谨慎开启Druid 有个“物理连接泄漏检测”机制参数是remove-abandoned和remove-abandoned-timeout。开启后连接被业务逻辑占用超过设定秒数仍不归还Druid 会强制回收同时把线程栈打印出来方便定位泄漏点。听起来很完美但我要强调一句不要让它在生产环境长期全量开启。原因很简单有些合法慢查询或长事务的持锁时间本来就长如果被 Druid 误判为泄漏强制回收底层连接上的事务会被异常中断数据一致性可能出问题。我的建议是在怀疑连接泄漏时先在预发或低峰期开一段时间打开log-abandonedtrue拿到具体的持连接线程栈定位到代码位置修复后再把开关关掉。相比长期开启这种方式更安全。通过一次泄漏定位我遇到过的典型场景是某段定时任务里try { getConnection() }后没有 finally close每天跑到固定时间连接数就涨一截最终把连接池打满。3. 手动集成与自动配置的前因后果这一节讲实际操作Maven 依赖怎么写、配置文件怎么配、为什么主类里要排除一个自动配置类。如果你已经对这部分很熟可以直接跳到第 4 节看监控配置。3.1 pom.xml 里加依赖用 Maven 管理依赖的话坐标其实很固定。下面这段是当前比较稳的版本写法如果你在 Spring Boot 2.x 下1.2.20 完全够用如果是 Spring Boot 3.x也建议选 1.2.20 之后的版本而不是文件仓库里最早的 1.1.x 版本dependency groupIdcom.alibaba/groupId artifactIddruid-spring-boot-starter/artifactId version1.2.20/version /dependency要注意引入了这个 starter 之后就不需要再额外引入druid核心包了starter 内部已经传递依赖了核心包重复引入反而可能出现版本冲突。另一个常见问题是很多项目同时有druid-spring-boot-starter和druid-spring-boot-3-starter这类变体其实不同版本面向不同 Spring Boot 版本别混用。如果你的 Spring Boot 版本比较高先去 Maven 仓库看一眼哪个 starter 版本适配不要闭眼选最新。3.2 配置文件从哪里生效大部分人不关心自动装配原理但少踩坑的前提是搞清楚配置前缀。spring.datasource.druid.*下的配置会被绑定到DruidDataSource。像stat-view-servlet和web-stat-filter这两个子配置是专门控制监控功能的。如果配置前缀写错最典型的表现是项目能启动连接池功能正常但监控页面打不开或者所有 Druid 参数都是默认值。一个很经典的坑是有人把login-username写到了spring.datasource下面启动后发现页面像是没配置账号密码其实是被放到了错误的层级。Druid 的监控配置必须挂在spring.datasource.druid.stat-view-servlet下面。这里要先有一份能连接数据库的基础配置再往下才谈监控。3.3 为什么要排除 DataSourceAutoConfiguration使用 starter 之后我习惯在主启动类上加上一行SpringBootApplication(exclude DataSourceAutoConfiguration.class) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }原因是 Spring Boot 的DataSourceAutoConfiguration也会尝试根据spring.datasource.type创建数据源而 druid-spring-boot-starter 自带的DruidDataSourceAutoConfigure同样会创建数据源。两者同时存在时配置不完整的情况下可能互相干扰表现是项目启动成功但数据源可能不是 Druid。排除掉 Spring Boot 默认的数据源自动配置让 Druid 的自动配置类接管管理行为会变得可控。等到了多数据源场景这个排除操作还会更显必要因为多个 DataSource Bean 的场景下Spring Boot 的默认处理逻辑很容易产生歧义。4. 生产监控慢SQL、Web监控与统计接入了 Druid 之后最能直接看到回报的是监控能力。这一节重点说监控页配置和慢SQL日志怎么落看完这部分你就知道 Druid 和“裸奔”数据源拉开差距的地方在哪。4.1 开启 Druid 监控页面监控页面依赖StatViewServlet放行路径默认是/druid/*。Druid 的监控页面不像普通接口需要自己注册 Servlet只要把参数打开就能自动装配。下面这段配置就是监控模块的最小可用配置建议直接在上一节的连接池配置后面追加spring: datasource: druid: stat-view-servlet: enabled: true url-pattern: /druid/* login-username: admin login-password: admin2018 reset-enable: false web-stat-filter: enabled: true url-pattern: /* exclusions: *.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*启动后访问http://ip:port/项目上下文/druid/index.html就能看到监控页。这里的login-username和login-password是硬账号务必改成一个足够强的密码。reset-enablefalse也很重要它把页面上的“重置”按钮关掉防止有人误操作把统计指标清零。如果项目里接了 Spring Security需要把/druid/**加入白名单否则页面会被安全框架拦下来表现是访问后 401 或 403。exclusions里的内容也别偷懒。WebStatFilter 在上面的配置里拦截了所有 URL如果不把静态资源排除掉每次请求图片、css、js 都计入统计会拉高无关请求的占比导致看面板时以为并发量很高实际全是静态资源流量。4.2 慢SQL记录到独立日志文件Druid 的慢SQL日志需要配合过滤器和日志框架一起用。在 Druid 的 stat filter 配置里把慢SQL阈值设成一个符合业务预期的值spring: datasource: druid: filter: stat: enabled: true log-slow-sql: true slow-sql-millis: 5000配合上面的配置StatFilter会把执行时间超过 5000 毫秒的 SQL 单独打印到日志通道里。接着在 logback.xml 或 log4j2.xml 里给druid.sql.SlowSql这个 logger 单独设置一个文件输出。下面是一段 logback 示例logger namedruid.sql.SlowSql levelinfo additivityfalse appender nameDRUID_SLOW_SQL classch.qos.logback.core.rolling.RollingFileAppender filelogs/druid-slow-sql.log/file rollingPolicy classch.qos.logback.core.rolling.TimeBasedRollingPolicy fileNamePatternlogs/druid-slow-sql.%d{yyyy-MM-dd}.log/fileNamePattern maxHistory30/maxHistory /rollingPolicy encoder charsetUTF-8/charset pattern%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n/pattern /encoder /appender appender-ref refDRUID_SLOW_SQL/ /logger这样做的收益很明显慢SQL不再和业务日志混在一起DBA 可以直接每天看独立文件按耗时排序找优化目标。日志滚动的策略也建议打开避免单个文件无限涨。你要注意如果 log-slow-sql 开了但日志文件里始终没有内容先检查 logger 名字再确认slow-sql-millis是否设置得过大以及是否真的有超过阈值的 SQL 执行。4.3 监控面板上的关键指标怎么看监控页面首页“数据源”一栏重点看活跃连接数、等待获取连接次数、获取连接耗时。如果活跃连接数长期贴着max-active且等待次数持续上涨说明连接池配置接近天花板要么调大max-active要么去分析哪条 SQL 持有连接时间过长。“SQL监控”页面可以直接看每条 SQL 的总执行次数、执行时间、返回行数排序后最耗时的 SQL 一目了然这就是接 Druid 后最直观的回报。“连接池”页面可以看连接创建次数、回收次数。正常情况下创建和回收不应该频繁抖动如果每隔一小段时间就有大量连接创建说明min-idle和max-active之间的震荡控制没做好连接在反复创建销毁白白消耗数据库资源。5. 与 MyBatis 和多数据源场景的整合Druid 作为数据源和 MyBatis 搭配是市面上的常见组合。这一节把单数据源和多数据源都捋一遍避免只配好了连接池却不知道怎么接业务层。5.1 MyBatis 单数据源整合如果你的项目只有一套数据库整合非常简单。先引入 MyBatis 的 starter版本上尽量和 Spring Boot 主版本对齐Spring Boot 2.7 用 2.3.x 的 mybatis-spring-boot-starter 没问题Spring Boot 3 需要选 3.0.xdependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.2/version /dependency然后保持上面的 Druid 数据源配置MyBatis 会自动拿 Spring 容器里的DataSource使用不需要额外写代码。Mapper 接口上加上Mapper注解即可。有一点值得注意如果你同时配置了 Druid 的连接池参数和 MyBatis 的 typeAliasesPackage二者互不干扰因为 MyBatis 只关心数据源对象本身不关心连接池内部怎么维护连接。我之前见过有人把连接池参数也写进 mybatis 配置里这没有作用还会混淆排查思路。5.2 多数据源手动创建 DruidDataSource当项目需要连接多个数据库比如主库和从库或者业务库分开就要手动创建多个数据源 Bean 了。推荐用DruidDataSourceBuilder配合ConfigurationProperties可以做到配置和代码分离Configuration public class DataSourceConfig { Bean(name primaryDataSource) ConfigurationProperties(prefix spring.datasource.druid.primary) public DataSource primaryDataSource() { return DruidDataSourceBuilder.create().build(); } Bean(name secondaryDataSource) ConfigurationProperties(prefix spring.datasource.druid.secondary) public DataSource secondaryDataSource() { return DruidDataSourceBuilder.create().build(); } }对应的 yml 可以这样组织spring: datasource: druid: primary: url: jdbc:mysql://127.0.0.1:3306/db_main username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver max-active: 20 secondary: url: jdbc:mysql://127.0.0.1:3306/db_report username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver max-active: 10注意DruidDataSourceBuilder.create().build()返回的是DruidDataSource配合ConfigurationProperties可以把spring.datasource.druid.primary下的连接池参数全部注入。多数据源下原来的spring.datasource.url整体就不需要了因为没有一个默认数据源能自动绑定。如果要走这段多数据源代码可以保留前面说的主启动类排除逻辑否则默认数据源自动配置可能出来插一脚把其中一个 DruidDataSource 顶掉。接着要处理 Mapper 扫描的归属问题。不同数据源需要使用不同的MapperScan并绑定对应的 SqlSessionTemplateConfiguration MapperScan(basePackages com.example.mapper.primary, sqlSessionTemplateRef primarySqlSessionTemplate) public class PrimaryMybatisConfig { Bean public SqlSessionFactory primarySqlSessionFactory(Qualifier(primaryDataSource) DataSource dataSource) throws Exception { SqlSessionFactoryBean factoryBean new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource); return factoryBean.getObject(); } Bean public SqlSessionTemplate primarySqlSessionTemplate(Qualifier(primarySqlSessionFactory) SqlSessionFactory factory) { return new SqlSessionTemplate(factory); } }第二个数据源照葫芦画瓢再写一套。这套东西初次写会觉得模板化但好处是清晰、没有黑魔法查问题的时候一眼能看出哪个 Mapper 绑定哪个库。我见过一些项目用 AOP 切面实现动态数据源切换省了不少样板代码但对新手来说太绕出了问题不好查建议先把基础版本跑通再考虑进阶方案。5.3 多数据源下的事务提醒多数据源不能直接指望一个Transactional同时管两个库。Spring 的默认事务管理器绑定单个数据源跨库事务要么用分布式事务方案要么接受最终一致性。在我的实际经验里很多手写多数据源的项目业务上很少真的需要强一致跨库事务更多是拆分主报表库真碰上一个写操作同时改两库的场景建议先用消息队列或其他补偿手段而不是硬靠Transactional。这里不展开分布式事务方案但你要理解数据源配了两个不等于事务能力跟着翻倍这个认知能帮你避开很多设计上的坑。6. 常见问题与排查技巧实录接入 Druid 的过程中下面这些问题我基本都见过。整理成速查表方便你直接对号入座不用每次都去查文档或翻旧项目。现象可能原因排查与解决启动报 Failed to configure a DataSource未配置 url/用户名密码或排除逻辑写错检查 spring.datasource 下是否有完整连接信息启动报 Cannot load driver classMySQL 驱动没引入或驱动类名写错确认 pom 中有 mysql-connector-j 依赖监控页访问 404url-pattern 或项目 context-path 配置不对确认访问路径是否包含上下文前缀监控页面空白只有标题stat-view-servlet.enabled 未开启或登录账号被框架拦检查 yml是否拼写为 stat-view-servlet慢SQL日志文件无内容未开启 log-slow-sql或阈值太大打开 log-slow-sql确认 druid.sql.SlowSql logger连接池一直涨到 max-active 后报超时连接泄漏或并发峰值超过池上限找持连接不释放的代码短期开 removeAbandoned 定位数据源不是 Druid 而是 HikariCP未排除默认数据源自动配置或 spring.datasource.type 未设置加 exclude配置 typeDruidDataSource与 Spring Security 冲突监控页被拦截安全框架默认拦截所有请求白名单放行 /druid/**6.1 连接池耗尽的现场排查流程这里单独展开一个最常让人慌的场景连接池耗尽。业务反映接口全部超时日志里出现大量GetConnectionTimeoutException这时按下面顺序排查比较快。首先确认当前活跃连接数。打开 Druid 监控页切到数据源面板看 activeCount如果等于 maxActive说明池子被占满。接着看连接池面板里的逻辑等待次数如果等待次数也在快速上涨说明请求已经排队。然后抓一份线程栈用 jstack 或 Arthas 都可以重点找卡在getConnection()上的业务线程。这些线程很可能在等锁而锁的持有者线程又卡在某条 SQL 或某个第三方调用上。最后顺着线程栈定位代码大概率能找到忘记关闭连接、事务范围过长、或者一个循环里反复拿连接没释放这些典型问题。如果线上情况紧急可以先临时把 max-active 调大一档缓解压力但不要作为长期方案。真正解决后要把连接池调回压测过的合理值不然数据库资源依然被白白占着。6.2 我踩过的坑和一些优化建议连着说了这么多最后分享几个真实教训。第一个是关于连接池大小的。早年我给自己项目配过 max-active50数据库是 4C8G 的小实例结果压测时数据库连接数的平均等待时间不降反升。原因很简单连接池把所有连接都建到数据库端数据库自身也扛不住。后来压出 20 的合理值才稳定。连接池不是越大越好一定要跟数据库规格匹配。第二个是排查慢SQL阈值。Druid 默认的 slow-sql-millis 不一定是你的标准但我碰到过团队把它改成 50ms结果慢SQL文件一天几个G低频慢SQL全被淹没。阈值建议先按 3 秒或 5 秒起步等团队优化完一批明显慢的再逐步下调到 1 秒。调阈值这件事要按团队实际接受度来不要为了“好看”把标准定得太严最后日志量失控。第三个是监控页密码。接 Druid 监控很快但把admin/admin这种默认账号留在生产环境等于把数据库全貌展示给任何能访问页面的人。我见过有同学图省事不改密码被安全扫描工具扫出来。改一个复杂密码成本极低收益极高。顺便检查一下reset-enable生产环境建议保持 false。第四个是关于 stat filter 的性能影响。Druid 统计本身有开销但通常很小。如果项目对性能敏感、追求极致可以关闭 stat只用 log 或 wall 过滤器。但大多数业务系统完全不需要担心这个开销换取的可观测性远远更值。你可以在压测时打开和关闭 stat filter 各跑一轮用数据决定要不要保留。最后说一个延伸技巧Druid 的监控数据可以通过 JMX 导出配合 Prometheus 和 Grafana 做长期趋势图。这样慢SQL数量、连接池水位就不再是“偶尔打开页面看一眼”而是变成可以告警的指标。我现在的习惯是连接池活跃数大于 15 持续 5 分钟就告警慢SQL 数量超过 10 条/小时就告警这些数据比空看页面可靠得多。回到开头那句话Spring Boot 默认的 HikariCP 很快但如果你正在被“连接池为什么打满”“慢SQL是哪一条”这种问题反复折磨趁早把 druid-spring-boot-starter 接上。配置不会花你太多时间后面查问题的时候你会觉得这半小时花得非常值。如果你还没在生产环境加过监控我的建议是先从今天的配置开始在开发环境跑一天看看数据长什么样再决定要不要上生产。