问题分析
慢SQL的直接危害
数据库连接池长时间被占用,导致线程堆积,请求阻塞。
未释放的 ResultSet、Statement 或 Connection 可能导致内存泄漏(例如未正确关闭资源)。
大结果集(如一次性加载百万条数据到内存)直接导致堆内存激增。
连锁反应
线程阻塞:大量线程因等待数据库响应而处于 TIMED_WAITING 或 BLOCKED 状态。
GC压力:频繁Full GC尝试回收内存失败,导致GC线程占用CPU资源。
CPU飙升:线程堆积引发频繁上下文切换,叠加GC压力,最终CPU利用率接近100%。
定位与排查步骤
确认慢SQL
通过数据库慢查询日志(如MySQL的 slow_query_log)定位执行时间过长的SQL。
使用 EXPLAIN 分析SQL执行计划,检查是否缺少索引或出现全表扫描。
线程堆栈分析
bash# 生成线程快照jstack > thread_dump.txt
搜索 BLOCKED 或 TIMED_WAITING 状态的线程,观察是否集中在数据库操作(如 jdbc.Connection 相关调用)。
内存与GC监控
bash# 实时GC状态jstat -gcutil 1000
关注 Old Generation(老年代)使用率是否持续高位,Full GC频率是否异常。
资源泄漏检查
使用 jmap 生成堆转储文件,通过MAT工具分析大对象或未释放的数据库资源:bashjmap -dump:live,format=b,file=heap_dump.hprof
解决方案
1. SQL优化
为WHERE条件字段添加索引(联合索引注意最左匹配原则)。
避免 SELECT *,改用明确字段列表。
分页查询优化:用延迟关联替代 LIMIT 100000, 10。
复杂查询拆分为多个简单步骤,减少锁竞争。
2. 连接池调优
java// HikariCP 示例配置HikariConfig config = new HikariConfig();config.setMaximumPoolSize(20); // 根据DB性能调整config.setConnectionTimeout(3000); // 超时快速失败config.setLeakDetectionThreshold(5000); // 检测未关闭的连接3. 代码层修复
强制资源释放:使用 try-with-resources 确保关闭:javatry (Connection conn = dataSource.getConnection(); PreparedStatement ps = conn.prepareStatement(sql); ResultSet rs = ps.executeQuery()) { // 处理结果}
结果集分页:避免一次性加载全部数据,改用流式查询:javastatement.setFetchSize(100); // 分批拉取数据
4. JVM调优
bash# 增加老年代空间,减少Full GC频率-XX:NewRatio=2 -XX:+UseG1GC # 对高延迟场景更友好-XX:MaxGCPauseMillis=200 # 目标暂停时间5. 熔断降级
集成Resilience4j或Hystrix,在数据库超时率达到阈值时触发熔断,避免级联故障。
关键监控指标
注意事项
- 谨慎使用异步线程:异步处理可能转移问题到其他线程池,需确保所有资源最终释放。
- ORM框架陷阱:Hibernate的N+1查询问题(启用 hibernate.generate_statistics 检测)。
- 连接池保活:配置 idleTimeout 和 maxLifetime 防止连接僵死。