wait_timeout不是减少死连接的手段,而是控制服务端主动清理空闲非交互式连接的时机;真正导致连接不释放的主因是应用层未close、事务未提交或连接池配置与wait_timeout未对齐,需查清实际值、统一配置并重启生效,同时确保maxlifetime严格小于wait_timeout并启用探活机制。

MySQL 的 wait_timeout 不是用来“减少死连接”的,而是用来控制服务端主动清理空闲连接的时机;真正卡住连接不释放的,90% 是应用层没 close、事务没提交,或连接池配置和它没对齐。
查清当前生效值,别信默认值
云数据库、Docker 镜像、发行版打包的 MySQL,wait_timeout 实际值差异极大:腾讯云 RDS 常为 300 秒,MySQL 官方 Docker 镜像默认是 28800 秒,某些 PHP 环境甚至设成 60 秒。不查就调,等于凭感觉改内存地址。
- 连上 MySQL 执行:
SELECT @@wait_timeout, @@interactive_timeout;—— 注意单位是秒,且这两个值必须一致,否则后续排查会绕弯子 - 别用
SHOW VARIABLES LIKE '%timeout%',它默认查会话变量;要看全局值,得用SHOW GLOBAL VARIABLES LIKE 'wait_timeout' - 临时设置
SET GLOBAL wait_timeout = 600只影响新连接,且重启即失效;生产环境必须写进配置文件
永久修改必须落盘并重启 mysqld
Linux/macOS 下找 /etc/my.cnf 或 /etc/mysql/my.cnf,只在 [mysqld] 段下加两行:
wait_timeout = 600 interactive_timeout = 600
务必删掉 [client] 段里的同名配置——那只是给 mysql 命令行用的,不影响服务端判定。
- 改完必须执行:
sudo systemctl restart mysql(不是reload) - 验证是否生效:
SELECT @@wait_timeout;再开一个新连接查一次,避免被旧会话缓存误导 - 如果用的是 MySQL 9.6.0(2026 年新版本),确认
container_aware=ON已启用,否则容器内wait_timeout可能被系统级限制覆盖
HikariCP/Druid 连接池参数比数据库更关键
把 wait_timeout 设成 600 秒(10 分钟),但 HikariCP 的 maxLifetime 默认是 30 分钟(1800000 毫秒),连接池根本等不到 MySQL 断它,自己先销毁了;反过来,如果池子 idleTimeout 是 10 分钟,而 MySQL 设成 1 小时,那连接大概率在被 MySQL 杀之前就被池子回收了。
-
maxLifetime必须严格小于wait_timeout,建议留 30–60 秒缓冲,比如 MySQL 设为 3600,HikariCP 就设 3500 - 启用
connection-test-query=SELECT 1(MySQL 8.0+ 推荐用isValid())和test-on-borrow=true,但注意这会增加每次获取连接的延迟 - 更轻量的做法是关掉
test-on-borrow,改用keepalive-time=30000(每 30 秒发一次SELECT 1检活),配合validation-timeout=3000
别让 wait_timeout 成为掩盖代码问题的遮羞布
调低 wait_timeout 只是兜底手段,根子永远在应用层。常见错误包括:
- Java 里
Connection/Statement/ResultSet没在finally或try-with-resources中close(),漏一个就可能 leak - Spring Boot + MyBatis 下,
@Transactional方法里直接return ResponseEntity<list></list>,事务未提交,连接就被线程卡住 - 事务里混用
SELECT ... FOR UPDATE和普通SELECT,后者触发一致性读,前者锁住范围,导致连接长期持锁不释放
最危险的是:你以为调低 wait_timeout 解决了问题,其实只是把连接泄漏从“8 小时后爆发”变成了“10 分钟后爆发”,日志里满屏 MySQL server has gone away,却没人去看 SHOW PROCESSLIST 里那些 Sleep 连接到底是谁建的、为什么没 close。











