事务未提交则行锁与MDL持续持有,导致其他事务阻塞、DDL卡住、undo堆积、MVCC变慢、连接耗尽;常见原因包括代码漏提交、异常吞没、长等待及客户端操作中断。

事务没提交,锁就一直不放
InnoDB 的行锁是跟事务绑定的,不是跟 SQL 绑定的。哪怕你只执行了一条 SELECT ... FOR UPDATE 或一个 UPDATE,只要事务没 COMMIT 或 ROLLBACK,锁就挂在那条记录(或间隙)上不动。其他事务想改同一行?只能排队等——State: Locked 或 Waiting for table metadata lock 就这么来的。
更麻烦的是元数据锁(MDL):一个未提交事务哪怕只查了张表,也会持有该表的 MDL 读锁。这时候你跑 ALTER TABLE,它得等这个读锁释放,而读锁要等到事务结束。DDL 卡住、主从延迟突增,八成源头在这儿。
Undo 日志越堆越多,MVCC 查询越来越慢
InnoDB 用 undo log 实现 MVCC,但旧版本能不能清理,取决于“最老的活跃事务”。只要有一个事务从 10 分钟前开始就没提交,所有比它早的 undo 版本都得留着。结果就是:
-
innodb_history_list_length持续升高(查SHOW ENGINE INNODB STATUS\G的 HISTORY LIST) - 普通
SELECT要遍历更长的版本链才能判断可见性,哪怕加了索引也变慢 - undo 表空间膨胀,甚至触发自动扩展,IO 压力间接上升
这类慢查在 EXPLAIN 里完全看不出异常,监控里也找不到慢 SQL,但它真实存在。
连接被占着,新请求直接进不了队列
每个事务至少占用一个连接。事务不结束,连接状态就卡在 Sleep 或 Query,但 PROCESSLIST.Time 不更新(它只记最近语句耗时)。你看到一堆 Sleep 连接,背后可能全是挂着的未提交事务。
连接池会不断尝试新建连接,直到打满 max_connections。这时新请求连不上数据库,报错 Too many connections,而不是“慢”,但根本原因还是老事务没走。
常见诱因包括:
- 代码里
if (xxx) return;忘了commit - 异常被
catch吞掉,没rollback - 事务里调了 HTTP 接口或 sleep(5),SQL 早就执行完了,事务还在挂机
- DBA 用客户端执行
BEGIN; SELECT ...;后关了窗口,没COMMIT
提交本身也开始变慢:redo log 和 fsync 成瓶颈
事务提交慢,往往不是逻辑问题,而是物理 IO 卡住了。默认 innodb_flush_log_at_trx_commit = 1,每次 COMMIT 都要 fsync redo log。如果磁盘响应慢(比如 iostat -x 1 显示 await > 10ms 或 %util > 80%),或者 innodb_log_file_size 太小导致频繁 checkpoint,就会让 COMMIT 阻塞在刷盘阶段。
验证方法:
- 用
strace -p $(pgrep mysqld) -e trace=fsync看线程是否长时间停在fsync(3) - 查
SHOW GLOBAL STATUS LIKE 'Innodb_os_log_pending_fsyncs',非零且持续上涨就是证据 -
SHOW ENGINE INNODB STATUS\G里对比Log sequence number和Log flushed up to,差距拉大说明刷盘追不上写入
这时候调大 innodb_log_file_size(需停库删 ib_logfile0/1 后重启)比优化 SQL 更有效。


















