深度解析 MySQL 与 Spring Boot 长耗时进程:从故障现象到根治方案(含 Tomcat 重启必要性分析)

一、典型故障现象与用户痛点

在高并发业务场景中,企业级 Spring Boot 应用常遇到以下连锁故障:

  • 用户侧:网页访问超时、提交表单无响应,报错 "服务不可用"。
  • 运维侧 :监控平台报警 "数据库连接池耗尽",Tomcat 日志频繁输出GetConnectionTimeoutException: wait millis 6000, active 100(等待 6 秒未获取连接,当前 100 个连接被占满)。
  • 数据库侧 :执行SHOW PROCESSLIST发现 167 个 MySQL 进程,其中 100 + 个进程Time字段显示 "3600"(已运行 1 小时),状态为Sending dataLocked

二、故障根因定位:从现象到本质

2.1 核心错误:GetConnectionTimeoutException

该异常的直接原因是连接池无法在指定时间内提供可用连接。结合 MySQL 长进程现象,根因可归纳为以下四类(附权威验证):

故障类型 现象特征 权威依据
连接池配置不合理 连接池maxPoolSize过小(如设为 50),但业务并发量达 100TPS,导致连接池被占满 HikariCP 官方文档:建议maxPoolSize为 CPU 核心数 ×2+1(参考HikariCP 配置指南
连接泄露 代码未关闭Connection/Statement,连接池idleConnections持续下降至 0 JDBC 规范:Connection必须显式关闭(JDK7 + 推荐try-with-resources自动回收)
慢查询 / 长事务 MySQL 进程Time字段超长(如 3600 秒),Info显示无索引的大表查询 MySQL 官方文档:SHOW PROCESSLIST可定位长查询(参考MySQL 8.0 文档
连接有效性失效 MySQLwait_timeout设为 3600 秒(1 小时),但连接池未验证连接存活,导致持有无效连接 HikariCP 文档:需配置connection-test-query验证连接(参考HikariCP FAQ

三、分阶段解决方案:从临时止血到根治

3.1 临时止血:手动终止 MySQL 长进程(用户无法访问时)

当用户已无法访问,需快速释放数据库资源:

步骤 1:筛选危险进程

通过以下 SQL 定位 "执行中且耗时超 600 秒" 的进程(避免终止Sleep状态的空闲连接):

sql

复制代码
SELECT ID, USER, HOST, DB, TIME, STATE, INFO 
FROM information_schema.processlist 
WHERE TIME > 600 AND STATE != 'Sleep';
步骤 2:安全终止进程

执行KILL [进程ID]终止长进程(注意:可能导致未提交事务回滚,需确认业务容忍度):

sql

复制代码
KILL 1234; -- 终止ID为1234的长查询进程  

3.2 关键问题:终止 MySQL 进程后,是否需要重启 Tomcat?

结论通常无需重启 Tomcat,但需满足连接池配置正确;若配置不当,可能需要重启。

3.2.1 无需重启的场景(连接池配置正确)

若连接池(如 HikariCP)配置了连接有效性验证超时回收机制,Tomcat 可自动回收无效连接并创建新连接:

  • 连接有效性验证connection-test-query):

    连接池在获取连接时,会执行轻量级 SQL(如SELECT 1)验证连接是否存活。若 MySQL 进程已被终止,连接失效,验证失败后连接池会丢弃该连接并创建新连接。

  • 超时回收机制idle-timeout/max-lifetime):

    空闲连接超过idle-timeout会被主动关闭;连接存活超过max-lifetime会被强制回收,避免持有老化连接。

3.2.2 需要重启的场景(连接池配置不当)

若未配置连接有效性验证,或idle-timeout大于 MySQL 的wait_timeout,可能出现以下问题:

  • 连接池持有已被 MySQL 关闭的无效连接(Connection对象未被销毁,但底层 TCP 连接已断开)。
  • 后续请求从连接池获取到无效连接,执行 SQL 时抛出SQLNonTransientConnectionException: No operations allowed after connection closed

此时需重启 Tomcat,强制销毁所有连接池中的无效连接,并重新初始化连接池。

3.2.3 验证是否需要重启(关键操作)

终止 MySQL 进程后,通过以下步骤判断是否需要重启:

验证项 操作方法 无需重启的特征 需要重启的特征
应用日志 查看 Tomcat 日志(如catalina.out 出现HikariPool-1 - Closing connection com.mysql.cj.jdbc.ConnectionImpl@xxx(无效连接被回收) 出现SQLNonTransientConnectionException: No operations allowed after connection closed(无效连接被重复使用)
连接池监控 访问/actuator/metrics(需启用 Spring Actuator) hikaricp.connections.idle(空闲连接数)逐渐上升,hikaricp.connections.active(活跃连接数)下降 hikaricp.connections.idle持续为 0,hikaricp.connections.pending(等待连接数)大于 0

3.3 中期治理:连接池与 MySQL 配置优化

3.3.1 HikariCP 连接池配置(解决GetConnectionTimeoutException

通过以下配置平衡连接利用率与稳定性(参考 HikariCP 官方推荐):

properties

复制代码
# application.properties  
spring.datasource.hikari.maximum-pool-size=20        # CPU核心数×2+1(如4核设为9,高并发可适当调大)  
spring.datasource.hikari.minimum-idle=5             # 最小空闲连接数(建议≤maxPoolSize)  
spring.datasource.hikari.connection-timeout=30000   # 连接获取超时时间(30秒,避免短时间重试)  
spring.datasource.hikari.idle-timeout=1800000       # 空闲连接超时(30分钟,小于MySQL的wait_timeout)  
spring.datasource.hikari.max-lifetime=3600000       # 连接最大存活时间(1小时,避免连接老化)  
spring.datasource.hikari.leak-detection-threshold=30000  # 连接泄露检测(30秒未关闭则日志报警)  
spring.datasource.hikari.connection-test-query=SELECT 1  # 验证连接存活(避免持有MySQL已关闭的连接)  
3.3.2 MySQL 配置(自动终止长查询 + 回收空闲连接)

通过以下参数从数据库层拦截长耗时操作(参考 MySQL 8.0 官方文档):

ini

复制代码
# my.cnf(MySQL配置文件)  
[mysqld]  
max_execution_time=5000       # 单个查询最大执行时间(5秒,超时自动终止)  
wait_timeout=3600             # 空闲连接超时(1小时,需大于连接池的idle-timeout)  
interactive_timeout=3600      # 交互式连接超时(与wait_timeout一致)  
slow_query_log=1              # 开启慢查询日志(记录执行超2秒的查询)  
slow_query_log_file=/var/log/mysql/slow.log  
long_query_time=2             # 慢查询阈值(2秒)  

3.4 长期根治:代码与事务优化

3.4.1 修复连接泄露(解决 "连接被永久占用")

错误代码示例(未关闭资源):

java

复制代码
// 反例:未使用try-with-resources,连接可能未关闭  
Connection conn = dataSource.getConnection();  
Statement stmt = conn.createStatement();  
ResultSet rs = stmt.executeQuery("SELECT * FROM big_table");  
// 业务逻辑...  
// 未显式关闭conn/stmt/rs!  

正确代码示例(自动关闭资源):

java

复制代码
// 正例:JDK7+使用try-with-resources自动关闭  
try (Connection conn = dataSource.getConnection();  
     Statement stmt = conn.createStatement();  
     ResultSet rs = stmt.executeQuery("SELECT * FROM big_table")) {  
    // 业务逻辑...  
} // 自动关闭conn/stmt/rs(无需finally)  
3.4.2 控制事务超时(解决 "长事务占用连接")

通过 Spring 的@Transactional注解设置事务超时时间(单位:秒),超时自动回滚并释放连接(参考Spring 事务文档):

java

复制代码
@Service  
public class OrderService {  
    // 事务超时30秒(包含查询、锁等待等所有操作)  
    @Transactional(timeout = 30)  
    public void updateOrder() {  
        // 执行可能耗时的SQL(如更新大表)  
        orderMapper.updateLargeTable();  
    }  
}  

四、效果验证与监控体系

4.1 验证自动恢复

手动终止 MySQL 长进程后,通过以下方式确认连接池自动重连:

  • 应用日志 :检查是否有HikariPool-1 - Closing connection(无效连接被回收)或HikariPool-1 - Added connection(新连接创建)日志。
  • 连接池监控 :通过 Spring Actuator 查看/actuator/metrics/hikaricp.connections.active(活跃连接数)是否下降,hikaricp.connections.idle(空闲连接数)是否上升。

4.2 搭建监控体系(预防复发)

  • 连接池监控 :通过 Prometheus+Grafana 监控 HikariCP 指标(如activependingidle)。
  • 慢查询监控 :使用pt-query-digest分析慢查询日志,定位高频慢 SQL(示例命令:pt-query-digest /var/log/mysql/slow.log > slow_report.txt)。
  • 事务监控:通过 APM 工具(如 SkyWalking)追踪事务耗时,识别超时事务。

五、总结:从 "救火" 到 "预防" 的完整闭环

通过 "临时终止→配置优化→代码修复→监控预防" 四步,可彻底解决长耗时进程引发的系统故障:

  1. 临时止血 :手动终止 MySQL 长进程,快速恢复用户访问;通常无需重启 Tomcat,但需确保连接池配置正确。
  2. 配置优化:调整连接池与 MySQL 参数,避免连接耗尽与无效连接。
  3. 代码修复:修复连接泄露、控制事务超时,从根源减少长连接。
  4. 监控预防:通过日志与指标监控,提前发现慢查询与连接异常。
相关推荐
杰克尼11 分钟前
mysql_day01
数据库·mysql
白宇横流学长18 分钟前
基于SpringBoot实现的冬奥会科普平台设计与实现【源码+文档】
java·spring boot·后端
Rover.x2 小时前
Netty基于SpringBoot实现WebSocket
spring boot·后端·websocket
中国胖子风清扬3 小时前
SpringAI和 Langchain4j等 AI 框架之间的差异和开发经验
java·数据库·人工智能·spring boot·spring cloud·ai·langchain
计算机学姐4 小时前
基于php的摄影网站系统
开发语言·vue.js·后端·mysql·php·phpstorm
Java水解4 小时前
【SpringBoot3】Spring Boot 3.0 集成 Mybatis Plus
spring boot·后端
计算机学姐4 小时前
基于php的旅游景点预约门票管理系统
开发语言·后端·mysql·php·phpstorm
czlczl200209254 小时前
高并发下的 Token 存储策略: Redis 与 MySQL 的一致性
数据库·redis·mysql
哈哈老师啊4 小时前
Springboot校园订餐管理系统k2pr7(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·spring boot·后端