mysql默认wait_timeout和interactive_timeout均为28800秒(8小时),生产环境应统一设为300或900秒,并确保连接池idletimeout≤wait_timeout,同时排查sleep连接背后的未提交事务。

wait_timeout 和 interactive_timeout 没设或设得太大
MySQL 默认这两个值都是 28800(8 小时),意味着空闲连接能挂 8 小时不释放。生产环境里几乎没人真需要这么长的空闲时间,结果就是连接池里的连接用完就“躺平”,越积越多,直到 max_connections 被占满,新请求直接报 Too many connections。
必须同时设置两个参数,且建议统一为 300(5 分钟)或 900(15 分钟):
-
wait_timeout控制 JDBC、ORM、API 服务这类非交互式客户端 -
interactive_timeout控制 Navicat、mysql 命令行等交互式工具 - 只改一个没用;设成不同值反而容易引发会话行为不一致
验证是否生效,一定要用:SHOW GLOBAL VARIABLES LIKE 'wait_timeout';
而不是 SHOW VARIABLES——后者查的是当前会话变量,不是全局配置。
应用层没正确关闭连接,或关闭时机错误
最常见的不是“完全没关”,而是“关得太早”。比如 PHP 中写成:
mysql_close(); echo $html;
问题在于:HTTP 响应还没发完,连接就被关了。Web 服务器(如 Apache、Nginx)可能还在缓冲或传输响应体,而 MySQL 连接已断,导致后续请求无法复用该连接,连接池只能新建连接——旧连接变 Sleep,新连接又来占坑。
正确顺序是:
-
echo $html;或return response;完成输出后再关连接 - 使用 PDO 或 mysqli 时,确保
$pdo = null或unset($pdo),触发析构释放 - 在框架中(如 Laravel、Spring Boot),优先依赖容器生命周期管理连接,而非手动
close()
连接池配置与 MySQL 超时不匹配
即使 MySQL 设了 wait_timeout = 300,如果连接池(如 HikariCP、Druid)的 idleTimeout 是 600000(10 分钟),它就会把连接留着,等 MySQL 主动踢人。结果就是连接池认为“我还健康”,MySQL 却早已 KILL 掉它——下次取连接时抛出 MySQL server has gone away,连接池再重建,形成重连风暴。
关键对齐原则:
- 连接池的
idleTimeout必须 ≤ MySQL 的wait_timeout - HikariCP 还要检查
maxLifetime,它应略小于 MySQL 的wait_timeout(例如设为280000ms) - Druid 需确认
minEvictableIdleTimeMillis和timeBetweenEvictionRunsMillis是否覆盖了空闲检测
Sleep 连接背后藏着未提交的长事务
别被 Command = 'Sleep' 蒙蔽。如果这个连接之前执行过 BEGIN 或修改语句,但没 COMMIT 或 ROLLBACK,它虽然显示 Sleep,实际仍持有行锁、MDL 锁、undo 段——这才是最危险的 Sleep。
快速识别方法:
- 查
information_schema.innodb_trx,看trx_started时间是否远早于当前时间 - 结合
processlist的Time列和Info列:若Info为空但Time> 300,大概率是事务卡住 - 执行
SELECT * FROM performance_schema.events_statements_current WHERE SQL_TEXT LIKE '%BEGIN%';找可疑会话
这种连接不能只靠超时清理——它可能已经阻塞了 DDL、拖涨 undo 表空间、甚至让从库复制延迟飙升。必须立刻定位源头代码,补上事务边界控制。
真正难处理的从来不是 Sleep 这个状态,而是它掩盖下的事务悬停、连接复用断裂和配置错位。调低 wait_timeout 只是止血,不是缝合伤口。











