长事务本身不直接抖动,但会放大性能抖动——它延长锁持有、堆积脏页、拉长MDL等待链,最终在查询或DDL时集中爆发;解决重点是切断连锁反应路径,而非盲目KILL。

长事务本身不直接“抖动”,但它是性能抖动的放大器和触发器——它让锁持有时间变长、脏页堆积更久、MDL等待链拉得更长,最终在某个查询或DDL操作时集中爆发。解决重点不是“杀长事务”,而是切断它引发连锁反应的路径。
查长事务:别只看SHOW PROCESSLIST,要盯INNODB_TRX
状态为Running或Sleep的线程未必是问题源;真正危险的是那些trx_state = 'RUNNING'但trx_started远早于当前时间的事务。
- 执行
SELECT trx_id, trx_started, trx_state, trx_mysql_thread_id, trx_query FROM information_schema.INNODB_TRX WHERE trx_started < DATE_SUB(NOW(), INTERVAL 60 SECOND),重点关注运行超 60 秒且仍没提交的事务 -
trx_query为空不代表没在干活,可能是事务开启后执行了SELECT就挂起,后续没发任何语句——这种最隐蔽 - 结合
performance_schema.events_statements_current查该线程最近执行的语句,确认是否卡在外部调用(如HTTP请求、消息队列消费)上
杀还是不杀:区分场景再动手
KILL
- 如果事务正在执行
UPDATE或INSERT且已修改大量行,KILL会触发回滚,期间仍持锁、刷日志、占CPU,抖动反而加剧 - 如果事务只是
BEGIN后空跑、没改数据,KILL能立刻释放MDL和行锁,适合处理阻塞ALTER TABLE的源头 - 对疑似卡在外部依赖的事务(比如
trx_query为空、trx_state = 'LOCK WAIT'但等的不是InnoDB锁),优先查应用日志确认是否超时未处理,而非直接KILL
防抖关键:控制脏页刷出节奏与LRU淘汰行为
长事务导致脏页长期滞留Buffer Pool,一旦被LRU淘汰或触发刷盘阈值,就会拖慢后续查询。
-
innodb_max_dirty_pages_pct设太低(如<40)会让刷页高频轻量,打断正常查询;SSD环境建议设为60–75,给后台刷新留平滑窗口 -
innodb_old_blocks_pct默认37,若观察到pages not made young占比持续>80%,说明冷脏页难被淘汰,可尝试降至25加速老化 - 避免
SELECT *类大扫描——它强制加载大量页,极易触发LRU尾部脏页刷盘阻塞;改用覆盖索引或分页+显式字段
根本规避:从应用层掐断长事务生成路径
配置参数只能缓解症状,应用不改,抖动永远在拐角等着。
- 禁止事务内嵌网络IO、文件读写、人工确认等不可控耗时操作;所有事务必须有明确
timeout(如Spring的@Transactional(timeout = 30)) - 批量操作必须排序:多表更新/插入按固定顺序(如先
inventory再order),避免因访问顺序不一致引发死锁+锁持有延长 - 使用
READ COMMITTED隔离级别替代REPEATABLE READ,减少间隙锁范围和持有时间,尤其对非唯一条件UPDATE有效
最容易被忽略的点:长事务抖动常和NUMA绑定失效共存。即使参数全调对,numastat -p $(pgrep mysqld)显示numa_miss高,说明脏页刷盘时跨节点分配内存,I/O毛刺就藏在这里——得用numactl --cpunodebind=0 --membind=0 /usr/bin/mysqld启动才真正生效。



















