真正治本需定位“开启事务未提交”的源头,通过INNODB_TRX与PROCESSLIST联查,筛选trx_state='RUNNING'、trx_query IS NULL且运行超300秒的事务,并结合COMMAND='Sleep'确认幽灵连接,再依锁等待关系谨慎KILL。

直接杀掉长事务线程能解燃眉之急,但真正要治本,得从“谁开了事务却不提交”这个源头下手——MySQL 5.7 的 INNODB_TRX 和 PROCESSLIST 联查,比看报错语句本身有用十倍。
查长时间 RUNNING 却没执行 SQL 的事务
锁等待超时(ERROR 1205 (40001): Lock wait timeout exceeded)的根因,90% 是某个事务早早就开了,却一直空挂着没 COMMIT 或 ROLLBACK。这类事务在 INNODB_TRX 里表现为:trx_state = 'RUNNING'、trx_query IS NULL、trx_started 时间远早于当前(比如 >300 秒)。它不跑 SQL,所以不占 CPU,也不进慢日志,但锁照持不误。
- 执行这句快速筛出嫌疑事务:
SELECT trx_id, trx_mysql_thread_id, trx_started, trx_state, trx_isolation_level, trx_query FROM information_schema.INNODB_TRX WHERE trx_state = 'RUNNING' AND trx_query IS NULL AND UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(trx_started) > 300;
- 重点看
trx_isolation_level:如果是'REPEATABLE READ',哪怕只执行过一条SELECT,事务一启就可能持有间隙锁,影响范围远超预期 - 记下
trx_mysql_thread_id,下一步要和连接状态对上号
确认是不是 Sleep 状态的“幽灵连接”
很多阻塞源不是正在跑 SQL 的线程,而是 COMMAND = 'Sleep' 却还挂着事务的连接——典型是应用连接池归还了连接,但没清理事务上下文。这种连接在 PROCESSLIST 里安静如鸡,却是锁等待的幕后黑手。
安装、修复、更新或打包一个基于 Python 和 PyMySQL 实现的 Windows `mysql` 命令。适用于用户希望在 PowerShell/CMD 中输入 `mysql` 时重定向到 Python 实现、需要交互式输入 MySQL 连接信息、需要同步到当前用户本地 bin 目录,或需要将该能力封装成可公开复用的 Codex skill。
- 用这句把 Sleep + RUNNING 组合揪出来:
SELECT p.ID, p.USER, p.HOST, p.DB, p.COMMAND, p.TIME, p.STATE, t.trx_id, t.trx_started FROM information_schema.PROCESSLIST p JOIN information_schema.INNODB_TRX t ON p.ID = t.trx_mysql_thread_id WHERE p.COMMAND = 'Sleep' AND t.trx_state = 'RUNNING' ORDER BY t.trx_started;
- 关键判断点:
TIME > 300且INFO为空 → 基本可断定是应用端异常或代码漏写commit/rollback -
HOST和USER字段必须核对:别误杀了User = 'system user'这类系统线程
KILL 线程前先看它卡在哪一步
盲目 KILL 可能导致业务数据不一致,尤其当被杀线程正在做关键更新时。务必先通过 INNODB_LOCK_WAITS 确认它是否真在阻塞别人,再决定动手时机。
- 查锁等待关系:
SELECT r.trx_id waiting_trx_id, r.trx_mysql_thread_id waiting_thread, b.trx_id blocking_trx_id, b.trx_mysql_thread_id blocking_thread, b.trx_query blocking_query FROM information_schema.INNODB_TRX r INNER JOIN information_schema.INNODB_LOCK_WAITS w ON w.requesting_trx_id = r.trx_id INNER JOIN information_schema.INNODB_TRX b ON b.trx_id = w.blocking_trx_id;
- 如果返回结果中
blocking_query是空值,或只是类似SELECT * FROM t WHERE id = ?这种简单语句,基本说明它没在干活,纯属“持锁睡觉” - 真要
KILL,用KILL [thread_id](不是KILL CONNECTION),MySQL 会自动回滚其未完成事务;调试阶段可用KILL QUERY [thread_id]先中断当前语句,观察是否释放锁
为什么批量操作特别容易触发这个问题
大事务 ≠ 大 SQL,而是事务生命周期太长。比如一个 UPDATE 批量改 10 万行,即使加了索引,InnoDB 也得逐行加锁、记录 undo log、维护 MVCC 版本链——整个过程锁一直没松开。更糟的是,事务里混了 HTTP 调用或文件 IO,锁空等时间直接拉满。
- 拆分批量操作:用
WHERE id BETWEEN ? AND ?分批次提交,每批控制在 100–500 行,避免单事务锁持有超 10 秒 - 检查
EXPLAIN输出:确保type是range或ref,绝不能是ALL(全表扫描会升级为大量行锁甚至表级意向锁) - 事务内不要做非 DB 操作:像发邮件、调第三方 API、等用户输入这类动作,一律挪到事务外
- 临时降隔离级别:对非强一致性场景,可在会话级执行
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;,减少间隙锁使用
最麻烦的从来不是报错那一刻,而是那个 Sleep 状态、TIME=1800、trx_query=NULL 的连接——它不报警、不打日志、不占资源,却能让后续所有写请求排队等死。盯住 PROCESSLIST 里的 Sleep,比盯着报错堆栈有用得多。

















