长事务会导致行锁长期持有、undo log无法清理、mdl锁阻塞ddl、连接池耗尽及主从延迟。其本质是事务未提交使x锁、next-key锁、mdl读锁持续存在,purge线程被阻塞,history list length上涨,mvcc查询变慢,极端时磁盘写满或应用雪崩。

长事务会让行锁“焊死”在数据上
InnoDB 的行锁不是语句执行完就释放,而是绑定在整个事务生命周期里。哪怕你只执行了一条 UPDATE t SET x=1 WHERE id=100;,只要没 COMMIT 或 ROLLBACK,这把 X 锁就一直钉在那行(还可能连带锁住索引间隙)。后果很直接:
- 其他事务对同一行做
UPDATE或SELECT ... FOR UPDATE会卡住,SHOW PROCESSLIST里状态变成Locked或Updating; - 若等待超时(默认
innodb_lock_wait_timeout = 50秒),对方报错ERROR 1205 (40001): Deadlock found when trying to get lock或Lock wait timeout exceeded; - 在
REPEATABLE READ隔离级别下,一个长事务的 next-key lock 可能覆盖大片索引范围,让看似不相关的 DML 也莫名阻塞。
Undo log 膨胀不是“占空间”那么简单
长事务不提交,InnoDB 就不敢清理它开始前产生的所有 undo log——因为 MVCC 快照读需要这些旧版本。这不是缓存老化问题,是 purge 线程被硬性阻塞:
-
history list length持续上涨(查SHOW ENGINE INNODB STATUS可见),说明 undo 日志积压严重; - 独立 undo 表空间可能写满,触发
innodb_undo_log_truncate自动截断,但该操作本身消耗 I/O; - undo 页频繁读写,拖慢所有快照读:每个
SELECT都得遍历更长的版本链,高并发下查询延迟明显上升; - 极端情况下,ibdata1 或 undo 表空间占满磁盘,新事务连
INSERT都失败。
MDL 锁阻塞 DDL 是最常被误判的问题
哪怕长事务只执行了 SELECT 并显式开启(BEGIN 后没 COMMIT),它也会一直持有该表的 MDL read lock。而 ALTER TABLE、DROP INDEX 等 DDL 必须等 MDL write lock,结果就是:
- DDL 卡在
Waiting for table metadata lock,且innodb_lock_wait_timeout对它完全无效; - 运维第一反应常是“复制线程挂了”或“网络抖动”,实际是主库上某个交互式客户端忘关了;
- MySQL 5.7+ 中,这种阻塞可无限期持续,除非 kill 掉长事务或等它自己超时(但默认无超时)。
连接池耗尽比数据库慢更难排查
每个长事务独占一个连接,且连接不会归还给池。假设你用 HikariCP,maximumPoolSize=20,20 个长事务跑满,新请求连获取连接都失败:
- 应用层抛
java.sql.SQLTimeoutException: Connection is not available或 HTTP 500; - DB 层
Threads_connected接近max_connections,但Threads_running可能很低,监控图看不出 CPU 或 QPS 异常; - 最容易被忽略的一点:连接断开(如客户端崩溃、超时踢出)后事务会自动回滚,但这个过程不是瞬时的——中间窗口期仍持有锁和 undo,照样造成阻塞。
INNODB_TRX 表里的 TRX_STARTED 和 TRX_STATE 是第一步,但真正要防住,得在应用层用 @Transactional(timeout = 5) 这类硬约束兜底——靠 DB 层参数兜不住。











