ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Spring Boot + MyBatis接入Druid连接池:SQL监控与生产调优实战

Spring Boot + MyBatis接入Druid连接池:SQL监控与生产调优实战 前段时间接手一个项目压测刚启动就疯狂刷Connection is not available, request timed out after 30000ms接口全挂。最难受的是日志里全是获取连接超时根本不知道哪条SQL把连接池拖垮。后来我直接把数据源换成Druid用druid-spring-boot-starter接进来打开监控页面的那一刻问题清清楚楚一条报表查询关联了十几张表单次跑了9秒多还一直霸占着连接不释放。这篇文章不打算只贴配置重点讲清楚为什么换Druid、怎么正确接入、监控页面怎么读、生产参数怎么调以及我踩过的那些版本和自动装配的坑。如果你正在做Spring Boot MyBatis的毕设或者维护一个存量项目应该都能直接对照着用。踩过连接耗尽、慢SQL、连接泄漏相关坑的人看完至少能省一晚上的排查时间。1. 为什么我在Spring Boot项目里把默认连接池换成了Druid1.1 默认连接池HikariCP到底缺在哪Spring Boot 2.x开始默认数据源就是HikariCP以轻量、快著称很多团队一直用它。但我不认为HikariCP“够用”它作为连接池本身是合格的可运维能力太弱。默认情况下你看不到每条SQL执行了多少次、单次耗时多长、连接池还剩多少连接、谁借了连接没还。线上出现数据库连接池被打满、接口超时要么靠DBA查慢日志要么靠猜效率太低。HikariCP不是没有指标能力配合Micrometer可以暴露给Prometheus但那是埋点之后再看聚合数据没法像Druid一样打开一个网页就看到完整的SQL明细、URI访问统计和连接持有时间。对于中小型团队、单体应用、毕业设计来说Druid把“看得见”这件事直接做到了开箱即用启动之后打开/druid/index.html问题基本能直接看到。1.2 Druid真正解决的三类问题我把Druid的独特价值归纳成三类这也是我换掉默认连接池的原因第一监控可观测。内置StatFilter统计SQL执行次数、耗时、连接获取耗时、返回行数不用额外打点访问监控页面就能看完整的数据。第二安全防御。WallFilter会在SQL执行前做语法级校验拦截注入、全表update/delete这类危险操作。我在日志里就见过某个查询条件被改写成恒真表达式wall直接把这条SQL拦下来等于给数据库入口加了一道应用层护栏。第三连接生命周期管理。除了常规淘汰空闲连接还提供removeAbandoned机制能检测“借出去超过N秒还没归还”的连接并强制回收这是排查连接泄漏的利器后面我会专门讲。下面这个表格是我平时选型时用的对比维度能力HikariCPDruid极致性能强常规够用内置SQL监控页面不支持StatViewServletSQL防火墙无WallFilter连接泄漏检测弱需外部集成removeAbandoned慢SQL日志需额外配置log-slow-sqlSpring Boot集成默认starter一键接入有人会担心Druid因为filter链在超高并发短SQL场景下性能不如HikariCP我实际压测下来绝大多数业务系统根本感知不到那零点几毫秒的差异。数据库连接池这类基础设施稳定性和排查效率比性能上的一点优势更重要。1.3 什么项目适合直接上Druid我的建议很简单基于Spring Boot MyBatis的单体项目尤其是毕设、中小后台管理系统、报表类应用直接上Druid接入成本低收益是肉眼可见的。需要防SQL注入、经常人肉定位慢SQL、希望连接池出问题时能快速查到是谁借了连接没还这些诉求都是Druid的强项。如果你的项目是纯计算型微服务数据库访问路径极短而且已经有了完善的可观测体系那保留HikariCP也没问题。工具是服务于场景的不强求统一。2. 第一次接入依赖版本、配置文件和自动装配原理2.1 版本坑Spring Boot 2.x和3.x的javax/jakarta问题依赖非常简单Maven里加一行dependency groupIdcom.alibaba/groupId artifactIddruid-spring-boot-starter/artifactId version1.2.23/version /dependency如果项目是Spring Boot 2.x1.1.x、1.2.x都能用如果已经在用Spring Boot 3.x记得选1.2.18以上的版本。原因是Spring Boot 3把servlet API从javax迁移到了jakarta老版本Druid的统计servlet在启动时会直接报ClassNotFound。这个坑我见过太多次很多人启动报错第一反应以为是包没引其实是版本问题。另外druid-spring-boot-starter本身不自带MySQL驱动数据访问还是得按原方案引入mysql-connector-j。用Gradle的话对应就是implementation com.alibaba:druid-spring-boot-starter:1.2.23没区别。2.2 最小可用配置从默认连接池切到Druid接入时我最开始还在用spring.datasource.url这种通用写法后来发现starter推荐把连接池专属参数都放在spring.datasource.druid.*命名空间下这样语义清楚也不容易和Spring Boot默认数据源的配置混淆。一个最小可用配置如下spring: datasource: druid: url: jdbc:mysql://127.0.0.1:3306/blog?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8 username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver initial-size: 5 min-idle: 5 max-active: 20 max-wait: 30000 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 filters: stat,wall,slf4j filter: stat: enabled: true log-slow-sql: true slow-sql-millis: 2000这段配置已经覆盖了日常90%的场景。几个容易忽略的点我在下面单独说尤其是filters这一行它直接决定哪些拦截器参与连接操作链写错或漏写会导致功能悄悄失效。同时建议显式声明数据源类型避免某些场景下Druid没有接管成功spring: datasource: type: com.alibaba.druid.pool.DruidDataSource2.3 自动装配为什么能用druid前缀接管数据源我见过很多人“配置能用就行”但从没想过starter是怎么把默认数据源抢过来的。借这个机会把原理讲透后面排查问题会轻松很多。druid-spring-boot-starter的jar包里通过META-INF/spring.factories注册了DruidDataSourceAutoConfigure这个自动配置类新版也会同时使用AutoConfiguration.imports。关键点是这个类上有AutoConfigureBefore(DataSourceAutoConfiguration.class)意思就是它要在Spring Boot默认的DataSource自动配置之前执行。DruidDataSourceAutoConfigure在容器中没有自定义DataSource时会创建一个DruidDataSourceWrapper读取spring.datasource.druid.*前缀下的属性并绑定到DruidDataSource同时把Spring Boot原本spring.datasource.url这类基础配置也兜底接管。所以加了starter之后依赖数据源的代码完全不用改事务管理器、MyBatis、JPA都照常工作。理解这个有什么实际用处排查“我明明加了Druid为什么数据源还是HikariCP”的时候你就能顺着两个方向查一是自动装配有没有被其他组件覆盖二是你是不是自己提前创建了DataSource Bean导致ConditionalOnMissingBean条件失效。方向对了比瞎改配置高效太多。3. 打开监控面板StatViewServlet和核心指标解读3.1 三步开启Druid后台监控Druid最值钱的是监控能力不打开监控等于白接。配置如下spring: datasource: druid: stat-view-servlet: enabled: true url-pattern: /druid/* login-username: admin login-password: admin123 reset-enable: false web-stat-filter: enabled: true url-pattern: /* exclusions: /druid/*,*.js,*.css,*.icostat-view-servlet提供后台页面web-stat-filter负责拦截Web请求做URI维度的统计。有三点必须提醒第一reset-enable一定设为false防止任何人在页面上重置统计数据生产环境下这是底线。第二登录账号密码不要用默认值这类监控页面一旦暴露在公网就是事故。第三exclusions要写全否则静态资源也会被统计监控页里塞满.js请求会干扰你分析。启动后访问http://localhost:8080/druid/index.html就能看到登录页。如果是Spring Boot 3.x页面路径不变但starter版本必须适配jakarta这点之前已经强调过。3.2 SQL监控、连接池监控和URI监控里的关键指标打开监控页后我建议按下面几个页签去读数据面板重点指标实际意义SQL监控执行次数、单次最大耗时、返回行数找慢SQL和全表扫描类SQL连接池监控活跃数、空闲数、等待获取次数、最大等待时间判断连接是否占满、是否有争抢URI监控请求次数、Jdbc执行次数、Jdbc耗时定位哪个接口调用数据库最多数据源初始/最小/最大连接数、当前活跃校验你的配置是否真正生效连接池监控里有个NotEmptyWaitCount代表线程在获取连接时发生等待的次数。这个值一旦持续增长说明max-active不够或者连接持有时间过长这是连接池参数需要调整的最直观信号。SQL监控里最有用的部分是能看到每条SQL的“单次最大耗时”和“执行最慢SQL列表”点进去能看到完整SQL文本配合返回的行数基本能判断是索引问题还是全表扫描。另外如果记录里“执行中”的SQL一直不消失说明有连接被卡住这时候连接池监控页的线程快照能派上用场它能找到连接是被哪段代码借出去的。3.3 用监控面板定位慢SQL的真实案例我实际排过一个问题某个列表接口平时50ms某个时间段突然涨到3秒。打开SQL监控发现有一条SELECT ... FROM order_detail WHERE user_id IN (...)执行次数特别高而且最大耗时持续上升。再看连接池监控活跃连接数已经贴着max-active走说明这条SQL把连接占住之后其他正常查询只能排队等着。定位之后改法也简单把IN查询拆成批量参数、对user_id加联合索引、必要时走异步预聚合。上线后接口回到80ms连接池的NotEmptyWaitCount也归零了。整个过程没有加一行打点代码全靠Druid监控页面已有的数据。这就是我认为Druid可观测能力真正值钱的地方——问题发生时不用先猜而是先看。4. 生产环境连接池调优参数背后的原理与坑4.1 连接池的启动、扩容和空闲回收机制连接池参数不能直接抄要根据接口吞吐量和数据库规格来定。先把核心机制讲清楚连接池一开始按initial-size创建一批连接业务请求来了如果连接不够会继续创建直到max-active当连接空闲超过min-evictable-idle-time-millis后台的DestroyConnectionThread会按time-between-eviction-runs-millis周期做回收保底保留min-idle个连接。如果不理解这套机制可以把连接池想象成银行柜台max-active是银行总共能开的窗口数min-idle是至少保持开着的窗口数max-wait是客户拿不到窗口时最多能等的秒数validation-query相当于柜员在空闲间隙跟客户确认一下“您还在吗”。这么看就很好理解为什么max-wait不能设0——等于让线程无限等。我一般建议设30000毫秒以内让调用方快速失败避免线程全部挂在获取连接上导致服务雪崩。这里还有一个进阶技巧如果initial-size配得比较大启动时Druid会同步初始化连接可能拖慢应用启动时间。可以加一个参数async-init: true让初始化异步化启动速度会明显改善适合连接数较多的项目。4.2 连接泄漏检测removeAbandoned的用与不用连接池最容易出的诡异问题就是“连接耗尽但SQL看起来都正常”。Druid提供了一个非常强力的工具配置如下spring: datasource: druid: remove-abandoned: true remove-abandoned-timeout-millis: 30000 log-abandoned: trueremoveAbandoned的原理是Druid会扫描所有已借出的连接如果某个连接从借出到归还的持有时间超过remove-abandoned-timeout-millis就判定为疑似泄漏直接强制关闭并记录日志。这个机制对排查忘记close连接、事务不提交这类问题非常有效。但我必须泼一盆冷水生产环境不要一上来就开。因为有些正常业务就是长事务比如大批量导入、复杂报表连接持有时间超过30秒很正常。一旦开启Druid会把正在跑的SQL连接强杀造成更大的事故。我的建议是先只开log-abandoned: true观察一段时间确认日志里稳定出现疑似泄漏再决定要不要真正开启回收。这个顺序能帮你避开“误杀正常事务”的坑。4.3 慢SQL日志和防火墙的配置建议慢SQL日志是我接入Druid之后最常用的功能。上一节配置里我写了log-slow-sql: true和slow-sql-millis: 2000效果是执行超过2秒的SQL会通过slf4j输出到应用日志。注意这里filters里必须包含slf4j否则日志不会输出。开这个功能的目标是让慢SQL出现在你熟悉的日志系统里而不是只能到监控网页去翻排查时间会明显缩短。WallFilter防火墙建议保持开启。它默认会拦截明显注入特征比如恒真条件、堆叠SQL也会拦截不带where条件的全表update/delete。但有些运营后台确实会写不带条件的清表SQL这时候别急着关防火墙先看日志里wall拦截的具体语句判断是业务真的需要还是SQL写漏了where。如果确认业务需要再针对性地调整wall的拦截策略而不是一键关掉。5. 排查实录接入Druid后最常见的五个问题5.1 数据源还是HikariCP自动装配没生效有段时间我在一个老项目里加了starter和druid前缀配置但Druid监控就是打不开。写了个接口打印dataSource.getClass().getName()出来的还是HikariDataSource。后来排查发现项目里有一个公共starter自己声明了Bean DataSource dataSource()导致Druid的自动装配遇到ConditionalOnMissingBean直接放弃。解决方式是把那个DataSource Bean删掉或者让自定义DataSource也加上ConditionalOnMissingBean同时显式设置spring.datasource.typecom.alibaba.druid.pool.DruidDataSource。如果你的项目同样打印出HikariDataSource按这个顺序查先看启动日志有没有Druid的初始化记录再看有没有自定义DataSource最后看druid前缀有没有拼写错误。最省事的方式永远是显式声明type。5.2 MySQL 8.x连接失败和时区报错接入druid-spring-boot-starter时很多朋友从老的Spring Boot 1.x项目迁移过来驱动还写着com.mysql.jdbc.Driver。MySQL 8以上必须用com.mysql.cj.jdbc.DriverURL建议加上useSSLfalseallowPublicKeyRetrievaltrueserverTimezoneAsia/Shanghai。时区不指定的报错非常经典错误信息里会出现乱码。这个问题和Druid本身无关但只要是MySQL 8 Spring Boot的组合几乎都会遇到。5.3 监控页面打不开、StatFilter不生效排这个问题的顺序先确认stat-view-servlet.enabled是true再确认访问路径是否精确配成/druid/*然后确认web-stat-filter的排除项是否写对。如果这些都对但还是404检查是不是在Servlet容器层面的某个过滤器里拦截了/druid路径。还有一个很低级的坑filters: stat,wall,slf4j里的逗号千万别配错。filter链一旦解析失败Druid通常会默默降级而不是报错症状就是页面能打开但没有任何数据或者干脆打不开排查起来很费劲。所以遇到这种情况第一件事就是回去看filters这一行。5.4 连接密码不想明文写在配置里如果项目有安全要求可以用Druid的ConfigTools生成加密密码。先生成密钥对和密文然后在配置里引用spring: datasource: druid: filter: config: enabled: true connection-properties: config.decrypttrue;config.decrypt.key你的公钥 password: 生成的密文这种方式至少避免密码明文出现在代码仓库里。公钥放配置里依然不算绝对安全但对绝大多数内部系统已经够用。我通常还会把公钥放到环境变量或配置中心进一步减少硬编码。5.5 与MyBatis、多数据源组合时的经验如果是Spring Boot MyBatis的传统组合Druid接入基本无感不需要改动Mapper代码唯一需要注意的是事务管理器会使用DataSource Bean确保你没有配置多套数据源然后又搞混了主从。多数据源场景我不推荐自己手工定义多个DruidDataSource再配Primary直接用druid-spring-boot-starter配合dynamic-datasource-spring-boot-starter然后在每个数据源分组下配置druid参数代码和配置都会干净很多。这里特别提一句如果自己定义多个DataSource一定要给其中主数据源加上Primary否则事务管理器会拿到错误的数据源运行期才会报错定位成本很高。我把Druid接进项目之后的真实体感最初两天只是配置和看监控后面越用越顺手。慢SQL日志帮我在没有DBA的情况下快速定位了好几个性能问题wall防火墙也真拦过一次注入攻击。如果你正在接入druid-spring-boot-starter我个人的顺序建议是先按第2节的最小配置把连接池切过去然后把监控打开再根据监控数据反推需要调整的参数不要一上来就堆一堆高级参数。最后补一句最容易被忽略的小技巧启动日志里出现{dataSource-1} inited才是Druid真正接管了数据源。看到这一句你这一版配置才算落地。
返回列表