【架构实战】连接池优化:数据库连接打满的排查与解决

发布时间:2026/7/17 17:36:27
【架构实战】连接池优化:数据库连接打满的排查与解决 连接池优化数据库连接打满的排查与解决一、连接池打满的惨烈现场2025年某电商平台大促凌晨1点告警爆发200个数据库连接全部占满新请求无法获取连接接口超时率飙升至60%下单流程全线瘫痪应用日志疯狂刷Connection is not available, request timed out after 30000ms紧急扩容数据库连接数到400短暂缓解后30分钟再次打满——问题不是连接不够是连接没被释放。根因排查慢查询阻塞连接3条SQL执行超过10秒占住连接不放事务过长下单事务包含4次RPC调用平均耗时3秒连接泄漏5个代码路径没有关闭连接连接池配置不合理maxLifetime30分钟连接频繁重建这不是个案。连接池问题是数据库相关的最高频故障类型。二、连接池原理与配置2.1 连接池工作原理连接池核心流程 应用请求连接 → 池中有空闲连接→ 是 → 分配连接 → 执行SQL → 归还池 → 否 → 等待timeout→ 超时 → 报错 连接池内部状态 ┌────────────────────────────────────────┐ │ Total MaxPoolSize (如200) │ │ Active 当前正在使用的连接数 │ │ Idle 空闲等待分配的连接数 │ │ │ │ Active Idle Total │ │ │ │ 当Active Total时 │ │ 新请求进入等待队列 │ │ 等待超过connectionTimeout则报错 │ └────────────────────────────────────────┘2.2 HikariCP核心配置HikariCP是当前性能最好的JDBC连接池Spring Boot默认。# HikariCP推荐配置spring:datasource:hikari:# 核心参数maximum-pool-size:20# 最大连接数重要见下方计算公式minimum-idle:5# 最小空闲连接数connection-timeout:30000# 获取连接超时时间30秒# 连接生命周期max-lifetime:1800000# 连接最大存活时间30分钟idle-timeout:600000# 空闲连接超时时间10分钟keepalive-time:60000# 保活探测间隔60秒# 连接验证connection-test-query:SELECT 1# 连接有效性检查MySQL# 泄漏检测leak-detection-threshold:60000# 连接泄漏检测阈值60秒2.3 最大连接数计算公式连接数计算公式 maximumPoolSize (core_count * 2) effective_spindle_count 示例 - 8核CPU 1个磁盘MySQL实例 → 8*2 1 17 - 8核CPU 2个磁盘MySQL主从读写分离 → 8*2 2 18 简化公式大多数场景 maximumPoolSize 10-20 就够了 关键认知 - 连接数不是越多越好 - 连接数过多会导致MySQL上下文切换开销增大 - MySQL每个连接占用约1MB内存 - 20个连接 vs 200个连接在8核机器上吞吐量差异10% 经验值 - 小型应用QPS100010-15 - 中型应用QPS 1000-500020-30 - 大型应用QPS500030-50配合读写分离连接数与吞吐量的关系连接数QPS平均延迟MySQL CPU1080012ms30%20150013ms45%50200015ms55%100210025ms80%200190050ms95%上下文切换开销结论连接数超过50后吞吐量反而下降。三、连接打满的排查流程3.1 标准排查步骤连接打满排查五步法 Step 1: 确认连接池状态 → 查HikariCP指标active/idle/waiting连接数 → 确认是否真的打满 Step 2: 查MySQL端连接 → SHOW PROCESSLIST看每个连接在干什么 → 重点关注Time列大的慢查询、State列异常的 Step 3: 分析慢查询 → 查MySQL慢查询日志 → 定位执行时间长的SQL Step 4: 分析连接持有时间 → 查应用端连接持有时间分布 → 开启leak-detection-threshold检测泄漏 Step 5: 分析业务逻辑 → 事务是否过长 → 是否有批量操作占住连接 → 是否有未关闭的连接3.2 排查命令# 1. 查看连接池状态Spring Boot Actuatorcurlhttp://localhost:8080/actuator/metrics/hikaricp.connections.activecurlhttp://localhost:8080/actuator/metrics/hikaricp.connections.idlecurlhttp://localhost:8080/actuator/metrics/hikaricp.connections.pending# 2. 查看MySQL连接状态mysql-eSHOW PROCESSLIST|wc-l# 连接总数mysql-eSHOW PROCESSLIST|grepSleep# 空闲连接mysql-eSHOW PROCESSLIST|grepQuery# 执行查询的连接# 3. 查看长时间占用的连接mysql-eSELECT * FROM information_schema.processlist WHERE Time 5 ORDER BY Time DESC# 4. 查看慢查询mysql-eSELECT * FROM mysql.slow_log ORDER BY start_time DESC LIMIT 20# 5. HikariCP泄漏检测日志grepConnection leak detection/var/log/app.log3.3 MySQL连接状态分析-- 查看当前连接详细状态SELECTID,USER,HOST,DB,COMMAND,TIME,STATE,INFOFROMinformation_schema.PROCESSLISTWHERETIME5-- 超过5秒的连接ORDERBYTIMEDESC;-- 常见STATE含义-- Sending data → 正在执行查询可能是慢查询-- Waiting for lock → 等待锁释放锁冲突-- Sleep → 空闲等待连接池中的空闲连接-- Statistics → 正在计算统计信息-- 查看连接来源分布SELECTSUBSTRING_INDEX(HOST,:,1)ASsource_ip,COUNT(*)ASconn_countFROMinformation_schema.PROCESSLISTGROUPBYsource_ipORDERBYconn_countDESC;四、连接池优化策略4.1 慢查询治理// 慢查询是连接打满的首要原因// 解决方案SQL优化 索引优化 查询拆分// 1. 紧急措施设置SQL执行超时ConfigurationpublicclassDataSourceConfig{BeanpublicDataSourcedataSource(){HikariConfigconfignewHikariConfig();// ... 基础配置 ...// 设置连接获取超时防止等待过久config.setConnectionTimeout(30000);// 30秒// 设置查询超时防止慢查询阻塞连接config.addDataSourceProperty(queryTimeout,10);// 10秒returnnewHikariDataSource(config);}}// 2. 慢查询SQL优化示例// 原始慢查询全表扫描3秒Query(SELECT * FROM orders WHERE status ? AND create_time ?)ListOrderfindOrders(Stringstatus,Datesince);// 优化添加索引 限制返回数量// 索引CREATE INDEX idx_status_time ON orders(status, create_time)Query(SELECT id, order_no, amount FROM orders WHERE status ? AND create_time ? LIMIT 100)ListOrderfindOrdersLimited(Stringstatus,Datesince);4.2 事务精简// 事务过长是连接打满的第二大原因// 错误在事务中做RPC调用连接被占3秒TransactionalpublicOrderResultcreateOrder(OrderRequestreq){// 1. DB操作创建订单5msorderRepo.save(order);// 2. RPC调用查询库存200ms← 不该在事务中inventoryService.checkStock(req.getSkuId());// 3. RPC调用预扣款300ms← 不该在事务中paymentService.prePay(req.getUserId(),req.getAmount());// 4. DB操作更新状态5msorderRepo.updateStatus(orderId,PRE_PAY);// 事务持有连接 5ms 200ms 300ms 5ms 510ms// 应该只持有5ms 5ms 10ms}// 修复事务只包含DB操作RPC调用移到事务外publicOrderResultcreateOrderFixed(OrderRequestreq){// 1. 事务外RPC调用不占连接inventoryService.checkStock(req.getSkuId());paymentService.prePay(req.getUserId(),req.getAmount());// 2. 事务内只做DB操作连接持有10msreturndoInTransaction(()-{orderRepo.save(order);orderRepo.updateStatus(orderId,PRE_PAY);returnnewOrderResult(orderId);});}4.3 连接泄漏检测与修复// 开启HikariCP泄漏检测// leak-detection-threshold 6000060秒未归还视为泄漏// 常见泄漏场景1手动获取连接未关闭publicvoidleakyCode(){ConnectionconndataSource.getConnection();// 获取连接// ... 执行SQL ...// 缺少 conn.close() → 连接泄漏}// 修复使用try-with-resourcespublicvoidfixedCode(){try(ConnectionconndataSource.getConnection()){// ... 执行SQL ...}// 自动关闭连接}// 常见泄漏场景2Spring事务未提交TransactionalpublicvoidleakyTransaction(){orderRepo.save(order);throwException();// 异常后事务回滚但某些情况下连接未归还}// 修复确保事务方法有完善的异常处理Transactional(rollbackForException.class)publicvoidfixedTransaction(){try{orderRepo.save(order);}catch(Exceptione){log.error(Order creation failed,e);throwe;// 确保触发事务回滚}}4.4 读写分离降低单库压力// 读写分离写操作走主库读操作走从库// 连接池压力从200降到100主库从库各100ConfigurationpublicclassReadWriteSplitConfig{Bean(masterDataSource)publicDataSourcemasterDataSource(){HikariConfigconfignewHikariConfig();config.setJdbcUrl(jdbc:mysql://master:3306/db);config.setMaximumPoolSize(15);// 写操作连接数较少// ... 其他配置 ...returnnewHikariDataSource(config);}Bean(slaveDataSource)publicDataSourceslaveDataSource(){HikariConfigconfignewHikariConfig();config.setJdbcUrl(jdbc:mysql://slave:3306/db);config.setMaximumPoolSize(20);// 读操作连接数较多config.setReadOnly(true);// 标记只读// ... 其他配置 ...returnnewHikariDataSource(config);}}// 动态路由根据方法自动选择数据源ComponentpublicclassDynamicDataSourceRouterextendsAbstractRoutingDataSource{OverrideprotectedObjectdetermineCurrentLookupKey(){// 读方法走从库写方法走主库returnTransactionSynchronizationManager.isCurrentTransactionReadOnly()?slave:master;}}五、连接池监控体系5.1 HikariCP指标监控// 注册HikariCP指标到PrometheusComponentpublicclassHikariCPMetrics{AutowiredprivateHikariDataSourcedataSource;AutowiredprivateMeterRegistrymeterRegistry;publicvoidregisterMetrics(){HikariPoolMXBeanpooldataSource.getHikariPoolMXBean();// 核心指标Gauge.builder(hikaricp.connections.active,pool,HikariPoolMXBean::getActiveConnections).description(Active connections).register(meterRegistry);Gauge.builder(hikaricp.connections.idle,pool,HikariPoolMXBean::getIdleConnections).description(Idle connections).register(meterRegistry);Gauge.builder(hikaricp.connections.pending,pool,HikariPoolMXBean::getThreadsAwaitingConnection).description(Pending connection requests).register(meterRegistry);Gauge.builder(hikaricp.connections.total,pool,HikariPoolMXBean::getTotalConnections).description(Total connections).register(meterRegistry);}}5.2 告警规则# Prometheus告警规则groups:-name:hikaricprules:# 连接池使用率超过80%-alert:HikariCPConnectionsHighexpr:hikaricp_connections_active / hikaricp_connections_total0.8for:5mlabels:severity:warningannotations:summary:连接池使用率超过80%# 等待连接的线程数超过5-alert:HikariCPConnectionsPendingexpr:hikaricp_connections_pending5for:2mlabels:severity:criticalannotations:summary:有{{ $value }}个线程等待数据库连接# 连接泄漏检测-alert:HikariCPLeakDetectedexpr:increase(hikaricp_connections_leak[5m])0labels:severity:critical六、踩坑总结坑点1连接池大小数据库max_connections问题应用HikariCP最大200连接MySQL max_connections200其他应用/监控工具无法连接。解决MySQL max_connections应大于所有应用连接池总和 管理连接预留。MySQL max_connections Σ(各应用maximumPoolSize) 50(预留) 示例3个应用(202015) 50 105坑点2maxLifetime与MySQL wait_timeout不匹配问题HikariCP maxLifetime30分钟MySQL wait_timeout8小时连接在MySQL端已失效但HikariCP不知道。解决HikariCP maxLifetime应比MySQL wait_timeout小几秒。# MySQL wait_timeout28800秒(8小时)# HikariCP maxLifetime应设置为 28800秒hikari:max-lifetime:1740000# 29分钟比wait_timeout小很多安全坑点3连接验证开销问题connection-test-querySELECT 1每次获取连接都执行一次高并发时开销可观。解决HikariCP默认使用Connection.isValid()JDBC4不需要test-query。删除connection-test-query配置。坑点4自动提交误关闭问题手动获取Connection后设置autoCommitfalse归还池时未恢复autoCommittrue后续使用该连接的事务行为异常。解决HikariCP默认在归还连接时重置autoCommit确保不要手动覆盖这个行为。七、总结连接池打满的本质是连接持有时间过长——要么是慢查询阻塞要么是事务过长要么是连接泄漏。核心要点连接数不是越多越好10-20足以应对大多数场景慢查询是连接打满的首要原因——治理慢查询是根本事务中不要做RPC调用——事务只包含DB操作连接泄漏检测阈值必须开启——60秒未归还视为泄漏maxLifetime必须小于MySQL wait_timeout读写分离是降低单库连接压力的有效手段排查口诀看池状态 → 查MySQL连接 → 找慢查询 → 测连接持有时间 → 查泄漏日志。作者架构实战团队日期2026-07-17标签#连接池 #HikariCP #数据库 #慢查询 #连接泄漏