线程池占满与数据库优化实战:从连接池耗尽到性能调优
本文最后更新于 2026-08-04 15:16
线程池占满与数据库优化实战:从连接池耗尽到性能调优
本文记录了一次生产环境的真实故障排查过程:批量替数任务执行时,数据库连接池耗尽导致服务不可用。从问题定位、根因分析到优化方案,完整呈现排查思路和解决策略。
一、问题背景
在执行替数任务时,两个调度任务同时报错:
- 任务一:
orderReplace— 数据库连接池耗尽 - 任务二:
asyncInsExecRepData— 数据库连接获取超时
两个任务的核心报错信息相同:
1 | |
关键信息解读:
| 字段 | 值 | 含义 |
|---|---|---|
wait millis |
30000 | 等待获取连接的时间(30秒) |
active |
50 | 当前活跃连接数 |
maxActive |
50 | 最大连接数 |
creating |
0 | 正在创建的连接数 |
结论:50 个连接全部被占用,新请求等待 30 秒后超时。
二、问题定位
2.1 活跃连接都在做什么?
从报错堆栈的 runningSqlCount 可以看到连接池中 SQL 的分布:
| SQL 类型 | 数量 | 说明 |
|---|---|---|
INSERT INTO SEND_SER_SET_DRAFT |
1 | 插入草稿表 |
INSERT INTO SEND_SER_SET_HIS |
47 | 插入历史表(占 94%) |
SELECT SEQ_xxx.nextval |
1 | 获取序列值 |
DELETE SEND_SER_SET_DRAFT |
1 | 删除草稿表 |
核心问题:47 个连接都在执行同一张历史表的 INSERT 操作,占用了 94% 的连接池资源。
2.2 为什么会有这么多并发 INSERT?
替数任务的核心逻辑是:对每条记录执行”删除草稿 → 插入草稿 → 插入历史”的操作。当批量任务并发执行时,大量线程同时向历史表写入数据,导致连接池被耗尽。
2.3 第二个任务的报错
1 | |
同样的连接池耗尽问题,等待 10 秒后超时。由于两个任务共享同一个数据库连接池,任务一耗尽了连接,任务二自然也无法获取连接。
三、根因分析
3.1 直接原因
批量替数任务并发度太高,大量线程同时执行 INSERT 操作,耗尽了数据库连接池。
3.2 深层原因
- 批量任务并发控制不足:没有限制同时操作数据库的线程数
- 历史表写入量大:每条记录都需要写入历史表,数据量大时 INSERT 语句多
- 连接池配置偏小:
maxActive=50在高并发批量场景下不够用 - 缺少流量控制:没有对批量操作进行限流或分批处理
3.3 调用链路分析
从堆栈信息可以还原调用链路:
1 | |
注意:整个批量操作在一个大事务中,事务持有连接的时间很长。
四、优化方案
4.1 连接池参数调优
1 | |
调优原则:
maxActive不是越大越好,需要根据数据库服务器配置和业务场景调整maxWait适当增大,避免短暂高峰时立即超时- 配合连接有效性检测,避免使用已断开的连接
4.2 批量任务分批处理
将大批量任务拆分为小批次执行,控制并发度:
1 | |
关键点:
- 每批独立事务,避免一个超长事务长时间占用连接
- 批次之间可以适当休眠,给连接池恢复时间
- 批次大小需要根据实际数据量和连接池大小调整
4.3 事务优化
问题:原实现中整个批量操作在一个大事务中,事务持有连接的时间过长。
优化:将大事务拆分为小事务:
1 | |
4.4 并发控制
使用信号量或线程池控制并发度:
1 | |
4.5 SQL 优化
批量 INSERT 优化:
1 | |
序列获取优化:
1 | |
五、监控与预警
5.1 连接池监控
Druid 自带监控功能,开启后可以实时查看连接池状态:
1 | |
5.2 关键指标预警
| 指标 | 预警阈值 | 说明 |
|---|---|---|
| 活跃连接数 / 最大连接数 | > 80% | 连接池即将耗尽 |
| 等待获取连接的线程数 | > 5 | 有请求在排队 |
| 连接获取平均耗时 | > 500ms | 连接池压力过大 |
| 长事务数量 | > 3 | 可能存在未释放的连接 |
5.3 日志增强
在关键位置添加日志,便于排查:
1 | |
六、优化效果
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 最大活跃连接数 | 50(经常打满) | 50(峰值约 30) |
| 连接获取超时 | 频繁发生 | 未再发生 |
| 批量任务执行时间 | 超时失败 | 正常完成 |
| 事务持有连接时间 | 数分钟 | 秒级 |
七、总结
7.1 排查思路
1 | |
7.2 优化核心原则
- 大事务拆小事务:减少单个事务持有连接的时间
- 批量操作分批执行:控制并发度,避免瞬间打满连接池
- 连接池参数合理配置:根据业务场景调整,不是越大越好
- SQL 批量化:减少数据库交互次数
- 监控预警:及时发现连接池压力,防患于未然
7.3 避坑指南
- ❌ 不要在批量操作中使用一个大事务
- ❌ 不要让批量任务无限制并发
- ❌ 不要忽视连接池的监控指标
- ✅ 分批处理 + 独立事务 + 并发控制
- ✅ 合理配置连接池参数
- ✅ 建立完善的监控预警机制
线程池占满与数据库优化实战:从连接池耗尽到性能调优
https://your-project-name.pages.dev/2026/07/25/threadpool-db-optimization/