长事务会持续占锁、阻碍undo清理、卡住MVCC快照,导致查询变慢甚至实例雪崩;可通过INNODB_TRX和performance_schema定位,但需结合状态、SQL及应用日志综合判断是否可杀。

长事务不是“慢一点”,而是持续占锁、拖住 undo 清理、卡住 MVCC 快照,轻则让 SELECT 变慢,重则直接拖垮整个实例。
长事务会把数据库拖成什么样
它不报错、不打满 CPU,但会让数据库在你看不见的地方持续恶化:
-
trx_rows_locked高意味着锁被长期持有,其他事务排队等锁,innodb_row_lock_waits指标会明显上升 - undo 日志无法 purge,
innodb_undo_log_truncated可能触发,ibdata1或独立 undo 表空间持续膨胀 -
innodb_history_list_length突增(常超 10000),说明 MVCC 版本链失控,简单查询也要遍历大量旧版本 - 主库
binlog刷盘延迟,从库Seconds_Behind_Master拉高,且slave_sql_running_state显示 “Waiting for table metadata lock” 类似卡点 - 连接池耗尽:一个长事务占一个连接,20 个长事务就能把默认
max_connections=200的实例压到雪崩边缘
用 INNODB_TRX 查当前活跃长事务
这是最直接、无需额外配置的排查方式,适用于所有 MySQL 5.6+ 版本:
SELECT trx_id, trx_mysql_thread_id AS thread_id, trx_started, TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS duration_sec, trx_state, trx_isolation_level, trx_query FROM information_schema.INNODB_TRX WHERE TIMESTAMPDIFF(SECOND, trx_started, NOW()) > 5 ORDER BY duration_sec DESC;
注意几个关键点:
- 别只看
duration_sec,要结合trx_state = 'RUNNING'和trx_query判断是否真在执行,还是卡在外部调用(比如 HTTP 请求没返回) -
trx_mysql_thread_id是 kill 的依据,不是PROCESSLIST_ID;杀之前先SELECT * FROM information_schema.PROCESSLIST WHERE ID = ?确认用户和 host - 如果
trx_query为NULL,说明事务已执行完 SQL,正卡在应用层——这时候 kill 没用,得查应用日志 - 高并发下建议加
LIMIT 20,避免大结果集拖慢查询本身
用 performance_schema 追源头(MySQL 5.7+)
当你要定位是哪个服务、哪台机器、哪段代码发起的长事务时,INNODB_TRX 不够用,必须关联线程和语句历史:
SELECT p.THREAD_ID, p.PROCESSLIST_ID, CONCAT(p.PROCESSLIST_USER, '@', p.PROCESSLIST_HOST) AS user_host, p.PROCESSLIST_INFO AS current_sql, t.TIMER_WAIT / 1000000000 AS trx_duration_sec, esh.SQL_TEXT AS last_executed_sql FROM performance_schema.events_transactions_current t JOIN performance_schema.threads p ON t.THREAD_ID = p.THREAD_ID LEFT JOIN performance_schema.events_statements_history esh ON p.THREAD_ID = esh.THREAD_ID AND esh.EVENT_NAME = 'statement/sql/select' WHERE t.STATE = 'ACTIVE' AND t.TIMER_WAIT > 5000000000 ORDER BY t.TIMER_WAIT DESC LIMIT 10;
这个查询依赖两个前提:
-
performance_schema必须启用:SET GLOBAL performance_schema = ON; -
events_statements_history消费者要打开:UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME = 'events_statements_history'; - 历史记录默认只保留 10000 条,线上建议提前设大:
SET GLOBAL performance_schema_events_statements_history_size = 100000;
如果 SQL_TEXT 为空,大概率是 ORM 自动开启事务后没显式提交,或者用了 START TRANSACTION 但没配对 COMMIT。
容易被忽略的陷阱
排查时最容易掉进这几个坑:
- 只查
INNODB_TRX却没看INNODB_LOCK_WAITS,导致误判“没锁冲突”——其实阻塞链已在形成,只是 victim 还没被选中 - 监控脚本里用
NOW()计算时长,但没考虑时区差异,导致告警阈值失效(比如 DB 用 UTC,脚本用本地时区) - 看到
trx_state = 'LOCK WAIT'就以为是死锁,其实可能是单边等待;真正死锁只会在SHOW ENGINE INNODB STATUS\G的输出里出现*** (1) WAITING FOR THIS LOCK TO BE GRANTED:块 - 用
KILL终止事务后,回滚可能持续数分钟——特别是涉及百万级更新的事务,SHOW PROCESSLIST里状态会变成Rolling back,此时不能重复 KILL
真正麻烦的从来不是“怎么找到它”,而是“怎么确认它能不能杀、杀了之后会不会更糟”。多数长事务背后,是应用层没设超时、事务里混了 RPC、或批量逻辑没分页。定位到线程后,第一反应不该是 kill,而是查对应应用的日志时间戳和调用栈。


















