应查information_schema.innodb_trx定位长事务,重点关注trx_started早于30秒、trx_state='running'且trx_query为空、trx_rows_locked/modified过大的事务;同时监控show engine innodb status中的history list length是否超10000,该值持续升高表明purge被阻塞,导致mvcc版本链过长、select遍历开销剧增。

查 information_schema.innodb_trx 看谁卡住了快照链
长事务不提交,InnoDB 就不敢清理它依赖的老版本 undo log,MVCC 版本链越拉越长,普通 SELECT(尤其没走索引的)就得遍历几十上百个版本才能定位到可见行。直接查 innodb_trx 是最快定位“钉子户”的方式:
-
trx_started时间早于 5 秒 —— 重点盯;早于 30 秒基本就是元凶 -
trx_state = 'RUNNING'且trx_query为空或为NULL,说明事务在执行耗时逻辑(比如外部 HTTP 调用、循环计算),但没发新 SQL,锁和快照却一直挂着 -
trx_rows_locked或trx_rows_modified值极大(如 >10 万),即使事务看似“空闲”,也已污染大量行版本
用 SHOW ENGINE INNODB STATUS 看 History list length 是否失控
这个命令输出里最该盯住的是 HISTORY LIST LENGTH 行。它代表当前未被 purge 的 undo log 条目数。在 MySQL 5.7 中,这个值持续 >10000 就危险,>50000 往往伴随明显查询变慢:
- History list length 持续上涨,但
purge done for trx's n:o 几乎不动 → purge 线程被长事务阻塞 - 输出中出现大量
---TRANSACTION XXXX, ACTIVE XXXX sec且后面没 SQL → 又一个沉默的长事务 - 注意:MySQL 5.7 默认
innodb_purge_threads=1,单线程 purge 在高写入+长事务场景下极易成为瓶颈
结合 performance_schema 定位事务源头(需提前开启)
如果业务没打日志,光靠 innodb_trx 只能看到线程 ID,找不到是哪个应用、哪段代码发起的。这时得依赖 performance_schema:
- 确认是否启用:
SELECT * FROM performance_schema.setup_instruments WHERE NAME = 'transaction';,若ENABLED为NO,需先执行UPDATE performance_schema.setup_instruments SET ENABLED='YES' WHERE NAME='transaction'; - 查活跃长事务:
SELECT THREAD_ID, EVENT_ID, TIMER_WAIT/1000000000 AS sec, STATE, WORK_COMPLETED FROM performance_schema.events_transactions_current WHERE STATE = 'ACTIVE' AND TIMER_WAIT > 5000000000; - 再关联
threads表获取客户端信息:SELECT t.PROCESSLIST_USER, t.PROCESSLIST_HOST, t.PROCESSLIST_DB, e.* FROM performance_schema.threads t JOIN performance_schema.events_transactions_current e ON t.THREAD_ID = e.THREAD_ID WHERE e.STATE = 'ACTIVE' AND e.TIMER_WAIT > 5000000000;
别忽略隔离级别对 MVCC 压力的放大作用
MySQL 5.7 默认隔离级别是 REPEATABLE READ,它要求事务内所有读都基于同一快照。这意味着:一个长事务开启后,哪怕只执行了一次 SELECT,后续所有其他事务产生的 undo log 都不能被清理——直到它结束。这点比 READ COMMITTED 严苛得多:
- 若业务允许(比如报表类、非强一致性场景),可临时把会话隔离级别降为
READ COMMITTED:SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; - 但切记:不能全局改
innodb_default_isolation,否则可能破坏已有事务语义;只应在明确可控的长任务连接中动态设置 - 真正治本的办法,还是拆分事务:把“查一堆数据 → 处理 → 更新一堆数据”改成“分批查、处理、更新”,每次事务控制在毫秒级
MVCC 性能衰退不是慢在查询本身,而是慢在版本遍历路径上。只要有一个事务拖着不提交,所有人的读操作都在替它擦屁股。排查时紧盯 trx_started 和 HISTORY LIST LENGTH 这两个数字,它们不会说谎,但容易被忽略。











