资讯动态

Druid 连接池调优:5 个步骤把连接获取与慢 SQL 压到毫秒级

发布时间:2026/10/29 19:48:03 来源:尧图企业网站定制
Druid 连接池调优5 个步骤把连接获取与慢 SQL 压到毫秒级【免费下载链接】druid阿里云计算平台DataWorks(https://help.aliyun.com/document_detail/137663.html) 团队出品为监控而生的数据库连接池项目地址: https://gitcode.com/gh_mirrors/druid/druid周五晚上告警群连续跳出GetConnectionTimeoutException通知。翻看 Druid 数据源监控页ActiveCount已经顶到MaxActiveWaitThreadCount还在涨连接获取时间从 1ms 恶化到 3 秒以上。本文基于源码拆解 Druid 连接池的两个核心指标——连接获取时间与 SQL 执行时间并给出 5 个可复现的调优步骤。读完你可以独立定位池内瓶颈、调整关键参数、并把慢 SQL 钉到具体代码行。连接获取时间从空座检查到叫号等待的完整链路现象接口 P99 突增耗时几乎全部花在getConnection()上而不是 SQL 本身。机制Druid 用一个数组存放池内可用连接poolingCount记录空位数。类比餐厅maxActive是餐位总数poolingCount是空闲座位activeCount是已坐人的座位。没有空位时新顾客不占位而是进入notEmpty条件变量队列等翻台maxWait就是你愿意排队的最长时长。源码证据一连接与空位计数private volatile DruidConnectionHolder[] connections; private int poolingCount; // 当前可用连接数位置DruidDataSource.java连接归还时putLast把连接塞回数组并唤醒一个等待线程boolean putLast(DruidConnectionHolder e, long lastActiveTimeMillis) { if (activeCount poolingCount maxActive || e.discard || this.closed || this.closing) { return false; } e.lastActiveTimeMillis lastActiveTimeMillis; connections[poolingCount] e; incrementPoolingCount(); // ... 省略 poolingPeak 记录 6 行 notEmpty.signal(); return true; }位置DruidDataSource.java取不到连接时pollLast进入等待循环while (poolingCount 0) { emptySignal(); // ... 省略 failFast 与过期判断 8 行 notEmpty.await(estimate, TimeUnit.MILLISECONDS); }位置DruidDataSource.java所以连接获取时间 ≈ 排队等待由maxWait与连接归还速率决定 连接创建/校验时间。排队时间归零的前提是池里始终有空位。SQL 执行时间用直方图桶区分普遍变慢与偶发卡顿现象平均执行时间看着正常个别请求却跑到秒级。机制JdbcSqlStat用原子变量维护总量与峰值无锁同时把每次执行耗时落进 8 个区间桶。类比高速路测速摄像头它不只记录平均车速还告诉你每个速度区间各有多少车——看一眼分布就知道是整体拥堵还是单点事故。private volatile long executeSpanNanoTotal; // 总执行时间纳秒 private volatile long executeSpanNanoMax; // 最大执行时间 // ... 省略 6 个区间桶字段 private volatile long histogram_10_100; // 10-100ms private volatile long histogram_1000000_more; // 1s位置JdbcSqlStat.java每次 SQL 执行完成后StatFilter触发回调写入统计StatFilterContext.getInstance().executeAfter(sql, nanos, null);位置StatFilter.java核心指标速查名称含义健康阈值源码位置ActiveCount借出的连接数 MaxActive 的 80%DruidDataSource.javaPoolingCount池内可用连接数 0DruidDataSource.javaWaitThreadCount排队等连接的线程数0DruidDataSource.javaExecuteSpanNanoMax最慢单条 SQL 耗时 1sJdbcSqlStat.javahistogram_1000000_more耗时 1s 的 SQL 次数0JdbcSqlStat.java配置与调优5 个可复现步骤Step 1 开启 stat 过滤器与监控页做什么在 Spring Boot 环境一次性配齐池参数、慢 SQL 阈值与监控入口。为什么不挂stat过滤器JdbcSqlStat没有数据来源监控页全是空表。spring: datasource: druid: url: jdbc:mysql://127.0.0.1:3306/demo username: root password: 123456 initial-size: 10 # 初始连接数 ≈ CPU核数×2避免冷流量集中建连 min-idle: 5 max-active: 50 # 最大活跃连接按并发峰值×1.5估算 max-wait: 3000 # 取连接最长等待 ms超时抛 GetConnectionTimeoutException time-between-eviction-runs-millis: 60000 filters: stat stat-view-servlet: enabled: true url-pattern: /druid/* login-username: admin login-password: admin验证访问/druid能看到 Stat Index 页数据源、驱动信息齐全 经验值initialSize过小冷流量会触发一波建连启动后头几秒 P99 最差。Step 2 开启 keepAlive 保活做什么keep-alive: true让驱逐线程周期性校验空闲连接。为什么MySQL 默认wait_timeout约 8 小时空闲连接会被服务端单方面掐断。不保活就会从池里借出死连接报错后才重建表现就是偶尔第一个请求很慢。验证压测机空闲 1 小时后发起首个请求不再抛CommunicationsExceptionJMX 中KeepAliveCheckCount持续递增。Step 3 把慢 SQL 阈值压到 500ms 并接入日志StatFilter statFilter new StatFilter(); statFilter.setSlowSqlMillis(500); // 超过 500ms 记为慢 SQL statFilter.setLogSlowSql(true); // 输出 SQL 耗时 调用栈为什么源码里slowSqlMillis默认是 3000msStatFilter.java。按 3 秒报警等看到日志时连接池往往已经排队了。验证人为执行一条SELECT SLEEP(1)确认日志打出 SQL、耗时与线程栈。Step 4 开启 PSCache 预编译缓存druidDataSource.setPoolPreparedStatements(true); druidDataSource.setMaxPoolPreparedStatementPerConnectionSize(20); // 每连接最多缓存 20 条为什么重复 SQL 省去发送 SQL 文本 → 服务端解析的往返对高频 CRUD 收益最明显。缓存上限设太小会频繁淘汰重建设太大浪费内存。验证监控页 SQL 列表中Executes上升而数据库侧 parse 耗时下降。Step 5 用 queryTimeout 兜底druidDataSource.setQueryTimeout(30); // 单条 SQL 最长执行 30s超时中断为什么池子再大也扛不住一条 SQL 占用连接跑 5 分钟。超时兜底保证最坏情况有界。⚠️ 注意以上参数改动建议先灰度一台节点压测验证再全量发布。故障复盘两起真实感事故的时间线案例一连接池耗尽服务不可用14:02告警群跳出GetConnectionTimeoutExceptionQPS 未变RT 从 20ms 涨到 3s。14:05打开/druid数据源页ActiveCount50MaxActive、PoolingCount0、WaitThreadCount37且每分钟 10。确认池被打满而非流量上涨。14:08在 Web URI Stat 页按请求最慢单次排序定位/order/create单次请求 2.1sJdbc执行数明显偏高14:12慢 SQL 日志显示SELECT * FROM orders WHERE created_at ?耗时 4.2s无索引调用栈指向订单定时对账任务连接在整个查询期间不归还。14:30给created_at补索引、对账任务改为分页查询。验证WaitThreadCount归零ActiveCount回落到 12。根因一句话一条无索引慢查询长期占用连接把归还速率拖到低于借出速率等待队列堆积直至池耗尽。修复ALTER TABLE orders ADD INDEX idx_created_at (created_at);预防queryTimeout保持 30s 兜底不要把removeAbandoned当作生产方案——源码在建池时就会打出removeAbandoned is true, not use in production警告DruidDataSource.java强回收会误杀合法长事务应优先靠慢 SQL 日志定位泄漏点。案例二ExecuteSpanNanoMax突增到秒级10:05监控告警ExecuteSpanNanoMax从 8ms 跳到 2.4s。10:07看直方图histogram_1000000_more从 0 变 17而 0-10ms 桶仍占 99%——是单点事故不是整体拥堵。10:11慢 SQL 日志锁到一条全表UPDATE调用栈指向凌晨批处理任务被误改到业务高峰执行。10:25改回分批每批 1000 行并调整调度窗口。10:30 后histogram_1000000_more归零。根因一句话批处理全表更新占用连接并拉长峰值执行时间。预防把histogram_1000000_more计数纳入告警阈值 0出现即通知不必等到超时异常。经验沉淀调优 checklist 与延伸问题stat过滤器与/druid监控页已启用并可访问maxActive/maxWait来自压测数据而非拍脑袋keepAlive已开启慢 SQL 阈值 500ms 且日志接入告警queryTimeout兜底已配置会看WaitThreadCount与直方图桶能区分排队问题与慢 SQL 问题如果业务存在突发流量静态的maxActive够用吗顺着 pool/ha/ 下的HighAvailableDataSource与各种 selector 实现看下去你会找到 Druid 给出的另一条路。【免费下载链接】druid阿里云计算平台DataWorks(https://help.aliyun.com/document_detail/137663.html) 团队出品为监控而生的数据库连接池项目地址: https://gitcode.com/gh_mirrors/druid/druid创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑