长事务会拖慢MySQL甚至导致卡死,因其持续持锁、阻塞MVCC清理、加重purge压力,引发锁等待和查询堵塞;根本原因是未提交导致undo日志与历史版本无法回收。

长事务为什么会让 MySQL 变慢甚至卡死
长事务本身不直接消耗 CPU,但它会持续持有锁、阻止 MVCC 清理旧版本、拖慢 purge 线程,最终让 innodb_row_lock_time 上升、show processlist 里一堆 Updating 或 Waiting for table metadata lock,连 SELECT 都可能被堵住。
根本原因不是“事务太长”,而是它没提交,导致 undo log 不能回收、历史版本堆在 buffer pool 里、其他事务反复读到旧快照而延长 rollback segment 压力。
- 显式开启但忘记
COMMIT/ROLLBACK的事务(比如应用异常退出、连接池未正确 close) - 大范围
UPDATE或DELETE没分批,单次执行超 10 秒 - 事务里混了慢查询、远程调用、文件操作等非数据库动作
怎么实时发现正在跑的长事务
别只看 show processlist——它只显示当前语句,不反映事务起始时间。真正要看的是 information_schema.INNODB_TRX,配合 INNODB_SESSIONS 和 PROCESSLIST 关联定位。
常用诊断 SQL:
SELECT trx_id, trx_started, trx_state, trx_mysql_thread_id,
TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) AS duration_sec,
trx_query
FROM information_schema.INNODB_TRX
WHERE TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 60;-
trx_state = 'RUNNING'但trx_query IS NULL:事务空闲中,可能卡在应用层 -
trx_state = 'LOCK WAIT':已发生锁冲突,得查INNODB_LOCK_WAITS - 注意
trx_mysql_thread_id对应PROCESSLIST.ID,可进一步 kill:KILL [thread_id]
如何安全清理卡住的事务而不丢数据
直接 KILL 是最快方式,但有风险:如果事务已写入大量 undo,回滚可能比运行还慢,反而更占资源。优先判断是否真要干掉它。
- 先确认该事务归属:查
PROCESSLIST的USER、HOST、DB,联系对应服务负责人 - 若确认无业务影响,且
duration_sec > 300(5 分钟),再KILL - 避免在高负载时批量 kill,防止 purge 线程瞬间过载;可加
SLEEP(0.1)控制节奏 - 对无法 kill 的(如状态为
ROLLING BACK),只能等——此时改参数innodb_fast_shutdown=0再重启也不解决问题
预防长事务:从配置和代码习惯入手
监控只是兜底,真正的防线在应用侧和 MySQL 配置上。很多团队只设了 wait_timeout,却忘了 interactive_timeout 和事务级超时控制。
- MySQL 5.7+ 可设
max_execution_time(仅对 SELECT),但对事务内语句无效;真正管用的是innodb_lock_wait_timeout=50(默认 50 秒),超时抛Lock wait timeout exceeded - 应用必须用 try/finally 或 context manager 确保事务结束,尤其注意异常路径下
rollback()是否执行 - 连接池要配
maxLifetime和idleTimeout,防止连接复用时携带残留事务上下文 - 定期跑脚本检查:
SELECT * FROM information_schema.INNODB_TRX WHERE trx_started < DATE_SUB(NOW(), INTERVAL 30 SECOND);
最常被忽略的一点:ORM 框架(比如 Django 的 transaction.atomic、Spring 的 @Transactional)默认不设超时,一旦底层连接卡住,整个事务就悬停在那里,监控也难及时捕获——得靠应用层主动埋点或 SQL 审计日志反查。










