长事务会阻塞DDL操作、拖慢InnoDB purge线程、放大锁冲突与死锁概率;应通过监控INNODB_TRX、设置超时参数及应用层事务模板主动拦截。

长事务会阻塞DDL操作
MySQL在执行 ALTER TABLE、DROP INDEX 等DDL语句时,需要获取表级元数据锁(MDL write lock),而活跃的长事务会一直持有该表的MDL read lock,导致DDL被挂起,直到长事务提交或回滚。现象是 SHOW PROCESSLIST 中出现大量 Waiting for table metadata lock 状态。
- 即使长事务只执行了
SELECT(显式开启事务后未提交),也会持续持有MDL读锁 - 5.7+ 版本中,
innodb_lock_wait_timeout不影响MDL等待,DDL可能无限期阻塞 - 线上紧急加索引或清理表时,常因一个未提交的
BEGIN; SELECT ...;卡住整个变更流程
长事务拖慢InnoDB purge线程
InnoDB通过purge线程异步清理undo日志中的过期版本。长事务的事务ID(trx_id)会成为全局最小活跃事务ID(min_trx_id),导致其开始后产生的所有undo记录都无法被purge。结果是:
-
information_schema.INNODB_TRX中TRX_ROWS_MODIFIED可能不大,但TRX_UNDO_BYTES持续增长 -
SHOW ENGINE INNODB STATUS的HISTORY LIST长度飙升(如 > 100万),严重时达数千万 - 磁盘空间占用增加,undo表空间膨胀;同时purge延迟加剧,MVCC快照变“重”,
SELECT扫描更多旧版本行
长事务放大锁冲突与死锁概率
事务越长,持有行锁、间隙锁的时间就越久,与其他并发事务的重叠窗口越大。尤其在高并发更新场景下:
- 一个长事务执行
UPDATE t SET x=1 WHERE id=100;后未提交,后续所有对id=100的DML都会被阻塞 - 若另一事务试图
INSERT INTO t VALUES (101, ...),且id是主键,InnoDB需加插入意向锁,与长事务持有的间隙锁冲突,直接触发死锁 - 应用层重试逻辑若未处理
Deadlock found when trying to get lock,可能形成雪崩式重试
如何快速定位和限制长事务
核心思路是监控 + 主动拦截。不要依赖事后排查。
- 定期查
SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(TIMEDIFF(NOW(), TRX_STARTED)) > 60;,重点关注TRX_STATE = 'RUNNING'且TRX_COMMAND = 'Sleep'的连接(通常是应用未关闭事务) - 设置
wait_timeout和interactive_timeout(如300秒),但注意:这仅对空闲连接生效,对已执行SQL但未提交的事务无效 - 更有效的是在应用侧统一使用带超时的事务模板,例如Java里用
@Transactional(timeout = 30);或在MySQL 5.7+ 中启用innodb_rollback_on_timeout=ON(注意:它只对锁等待超时生效,不适用于普通长事务) - 关键业务库可配置
max_execution_time=30000(单位毫秒),配合SET SESSION MAX_EXECUTION_TIME=30000;限制单条SQL运行时长,间接抑制长事务内SQL失控
SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE, INFO FROM information_schema.PROCESSLIST WHERE TIME > 60 AND COMMAND != 'Sleep';
真正棘手的不是“怎么查”,而是那些不走标准ORM、手动BEGIN/COMMIT又忘记异常兜底的脚本——它们往往藏在定时任务或运维工具里,得靠审计日志和业务梳理才能揪出来。











