mysql线程处于sleep状态本身正常,问题在于长期不释放:需同步调整wait_timeout与interactive_timeout(建议设为300秒并写入my.cnf),且连接池max-lifetime必须严格小于wait_timeout(至少预留30秒缓冲),否则会导致“mysql server has gone away”错误。

MySQL线程处于 Sleep 状态本身不是错误,而是正常状态——说明连接已建立、当前无活跃查询,正空闲等待下一条指令。真正的问题是:为什么它“一直”不释放?核心在于服务端超时机制和客户端行为没对齐。
wait_timeout 和 interactive_timeout 没生效或设得过大
这两个参数控制 MySQL 主动断开空闲连接的时间,默认都是 28800(8 小时),远超多数业务实际需要。
-
wait_timeout影响 JDBC、ORM、连接池等非交互式客户端 -
interactive_timeout影响 Navicat、mysql 命令行等交互式工具 - 必须同时设置,且值一致;只改 session 变量(
SET wait_timeout=60)无效,要改GLOBAL - 临时生效命令:
SET GLOBAL wait_timeout = 300;和SET GLOBAL interactive_timeout = 300; - 永久生效需写入配置文件(如
/etc/my.cnf的[mysqld]区块),否则重启即还原
应用层用了连接池但没配 max-lifetime 或配错了
现代 Java/Go/Python 应用基本都走连接池(HikariCP、Druid、SQLAlchemy pool),Sleep 连接长期不退,往往不是 MySQL 不杀,而是连接池“舍不得放”。
- HikariCP 的
max-lifetime必须严格小于 MySQL 的wait_timeout,建议至少留 30 秒缓冲 - 例如 MySQL 设了
wait_timeout = 3600(1 小时),HikariCP 就得设max-lifetime = 3500 - 如果
max-lifetime >= wait_timeout,连接池可能把已被 MySQL 关闭的连接再分配出去,下次执行直接报MySQL server has gone away - 别依赖
idle-timeout—— 它只管连接在池里空闲多久,不管数据库侧是否还接受它
程序没正确归还连接,或用了持久连接(pconnect)
尤其在 PHP 老项目或脚手架未规范封装的场景中,容易漏掉连接释放逻辑。
- PHP 中用
mysql_pconnect()或PDO::ATTR_PERSISTENT => true,调用mysql_close()或$pdo = null都不会真正断开连接 - 短连接也需确保
mysql_close()/$pdo = null/conn.close()在 finally 或 try-with-resources 中执行 - ORM 如 MyBatis、Hibernate 通常自动管理,但手动获取
Connection后必须显式 close - 注意:连接池里的
close()实际是归还,不是销毁;销毁由池自己按策略决定
查出来的 Sleep 线程 ID 被 kill 后又快速复现
这不是 MySQL 没杀干净,而是客户端(或连接池)立刻重连——说明问题不在服务端清理能力,而在上游不断新建连接却不释放。
- 先确认是谁在建连接:
SHOW PROCESSLIST看User和Host列,定位来源应用 - 检查该应用日志,看是否有频繁重连、连接泄漏(如未 catch 异常导致 close 被跳过)
- 用
SHOW GLOBAL STATUS LIKE 'Threads_connected'对比峰值和常态,判断是否持续增长 - 临时应急可写脚本定期 kill 超时 Sleep:
SELECT CONCAT('KILL ',id,';') FROM information_schema.processlist WHERE Command='Sleep' AND Time > 600;,但治标不治本
最易被忽略的一点:连接池和 MySQL 的超时配置必须错开,且错开方式不是“差不多”,而是“严格小”。很多人设了 wait_timeout = 600,却把 HikariCP 的 max-lifetime 也设成 600,结果就是连接刚被 MySQL 关,池子还没反应过来就把它发给了下一个请求——然后崩。











