MySQL 8.0 长事务排查
在 MySQL 数据库运维中,长事务是常见的性能 “隐形杀手”—— 它可能导致锁等待堆积、事务日志(undo/redo log)膨胀、甚至拖慢整个实例的并发能力。尤其是 MySQL 8.0 版本对事务监控机制做了优化,借助
performance_schema
和sys
库,我们能更精准地定位长事务问题。本文将从 “为什么关注长事务” 出发,逐步讲解如何查看活跃长事务、溯源历史执行语句,并给出实用的处理建议。1. 为什么要关注 MySQL 8.0 长事务?
长事务通常指运行时间超过预期阈值(如 10 秒、1 分钟,具体因业务而定)的活跃事务。它的危害主要体现在三点:
- 锁资源占用:未提交的长事务会持续持有行锁 / 表锁,导致其他请求阻塞,引发 “锁等待超时”。
- 日志膨胀:MySQL 会为每个事务记录 undo log,长事务的 undo log 无法及时回收,可能导致
ibdata1
文件无限增大。 - 性能损耗:长事务会占用事务 ID(TRX_ID),若并发量大,可能触发 “事务 ID 回绕” 风险(尽管 MySQL 8.0 已优化,但仍需警惕)。
因此,定期排查长事务是 MySQL 运维的核心操作之一。
2. 基础操作:查看当前活跃长事务
MySQL 8.0 推荐通过
performance_schema
(性能字典表)关联sys
库查询长事务 —— 相比旧版本的information_schema.INNODB_TRX
,它能提供更丰富的事务信息(如运行时长、当前执行语句、用户信息)。2.1 核心查询 SQL
直接执行以下语句,可获取当前 Top 10 最长的活跃事务:
SELECT
thr.processlist_id AS mysql_thread_id, -- 事务对应的MySQL线程ID(关键,用于后续排查/终止)
CONCAT(PROCESSLIST_USER, '@', PROCESSLIST_HOST) AS `User`, -- 发起事务的用户及主机
Command, -- 线程状态(如Sleep、Query、Commit)
FORMAT_PICO_TIME(trx.timer_wait) AS trx_duration, -- 事务运行时长(自动转换为秒/微秒,易读)
current_statement AS `latest_statement` -- 事务当前执行的SQL语句
FROM
performance_schema.events_transactions_current trx -- 存储当前活跃事务的表
INNER JOIN performance_schema.threads thr USING (thread_id) -- 关联线程表,获取线程信息
LEFT JOIN sys.processlist p ON p.thd_id = thread_id -- 关联sys库,获取当前执行语句
WHERE
thr.processlist_id IS NOT NULL -- 排除无效线程
AND PROCESSLIST_USER IS NOT NULL -- 排除无用户标识的事务
AND trx.state = 'ACTIVE' -- 只保留“活跃中”的事务
GROUP BY
thread_id, timer_wait
ORDER BY
TIMER_WAIT DESC -- 按事务时长倒序,优先看最长的
LIMIT 10; -- 只看前10条,避免结果过多
2.2 结果解读
以下面查询结果为例,我们来分析关键信息:
+-----------------+--------------------+---------+--------------+-------------------------------------------------------------------+
| mysql_thread_id | User | Command | trx_duration | latest_statement |
+-----------------+--------------------+---------+--------------+-------------------------------------------------------------------+
| 3062872 | root@192.168.11.23 | Sleep | 5.13 s | NULL |
| 3062771 | root@localhost | Query | 309.02 us | SELECT thr.processlist_id AS m ... ER BY TIMER_WAIT DESC LIMIT 10 |
+-----------------+--------------------+---------+--------------+-------------------------------------------------------------------+
重点关注 3 个核心字段:
- mysql_thread_id:线程 ID,后续终止事务需用它(如
KILL 3062872
)。 - Command = Sleep:这是最需要警惕的状态!
Sleep
表示线程处于 “空闲但未释放事务” 状态 —— 通常是业务代码未及时执行COMMIT
/ROLLBACK
,导致事务挂起(默认超时由interactive_timeout
控制,默认 8 小时)。 - latest_statement = NULL:
Sleep
状态的事务无 “当前执行语句”,说明它已停止 SQL 执行,但事务未结束,属于典型的 “遗忘事务”。
而第二条
Command = Query
的事务仅运行 309 微秒(极短),且语句是我们当前的查询语句,属于正常操作,无需关注。3. 进阶操作:查看长事务的历史执行语句
基础查询可能遇到 “latest_statement 为 NULL” 的情况(如 Sleep 事务),此时无法直接看到事务之前执行过的 SQL。MySQL 8.0 可通过开启
performance_schema
的 “历史语句记录” 功能,溯源长事务的完整执行过程。3.1 第一步:开启历史语句监控
默认情况下,
performance_schema
的events_statements_history_long
和events_transactions_history_long
是关闭的(为了减少性能开销),需手动开启:UPDATE performance_schema.setup_consumers
SET enabled = 'yes'
WHERE name LIKE 'events_statements_history_long' -- 记录所有语句的历史(包括事务内的)
OR name LIKE 'events_transactions_history_long'; -- 记录所有事务的历史
- 开启后,MySQL 会将事务执行过的 SQL 语句存入
events_statements_history
表(短期)和events_statements_history_long
表(长期,容量更大)。 - 注意:开启该功能会增加少量性能开销(约 1%-5%),生产环境建议按需开启,而非长期开启所有监控项。
3.2 第二步:查询长事务的历史 SQL
根据基础查询中获取的
mysql_thread_id
(如 3062872),执行以下语句可查看该事务之前运行过的所有 SQL:SELECT
-- 计算事务开始时间(当前时间 - 事务运行时长)
DATE_SUB(NOW(), INTERVAL (
SELECT variable_value
FROM performance_schema.global_status
WHERE variable_name = 'UPTIME' -- 数据库启动至今的秒数
) - TIMER_START*10e-13 SECOND) AS trx_start_time,
SQL_TEXT -- 事务执行过的SQL语句
FROM
performance_schema.events_statements_history
WHERE
-- 通过mysql_thread_id找到对应的事务ID(EVENT_ID)
nesting_event_id = (
SELECT EVENT_ID
FROM performance_schema.events_transactions_current t
LEFT JOIN sys.processlist p ON p.thd_id = t.thread_id
WHERE conn_id = 3062872 -- 替换为你的mysql_thread_id
)
ORDER BY
event_id; -- 按SQL执行顺序排序
执行后,你会看到该长事务从启动到当前的所有 SQL 操作(如
BEGIN
、SELECT * FROM big_table
、UPDATE
等),从而定位到导致事务变长的具体业务 SQL。4. 长事务的常见问题与处理建议
排查出长事务后,需结合业务场景处理,避免盲目终止事务:
4.1 区分 “正常长事务” 与 “异常长事务”
- 正常长事务:业务必需的操作(如大表全量同步、数据归档),通常在低峰期执行,且有明确的超时控制。这类事务无需处理,只需监控其执行进度。
- 异常长事务:多为代码 bug 导致(如未提交事务、死循环)或慢查询引发(如无索引的大表查询)。这类事务需优先处理。
4.2 处理异常长事务的步骤
- 记录现场:先执行
SHOW ENGINE INNODB STATUS\G
,保存事务的锁信息、回滚进度,便于后续分析。 - 通知业务:确认事务对应的业务模块(通过
User
和SQL_TEXT
判断),通知开发人员评估终止事务的影响。 - 终止事务:若确认无影响,执行
KILL [mysql_thread_id]
(如KILL 3062872
),MySQL 会自动回滚该事务(回滚时间取决于事务大小,大事务需耐心等待)。 - 优化根源:修复代码 bug(如确保事务结束后执行
COMMIT
)、优化慢查询(如添加索引)、缩短事务范围(如拆分大事务为小事务)。
5. 注意事项
- performance_schema 的性能开销:开启过多
setup_consumers
(如events_statements_history_long
)会增加 CPU 和内存开销,生产环境建议仅开启必要的监控项,且定期清理历史数据。 - 参数调优:若频繁出现 Sleep 状态的长事务,可适当减小
interactive_timeout
和wait_timeout
(如设为 300 秒),让空闲事务自动释放,但需提前与业务确认(避免影响长连接业务)。 - 定期监控:将长事务排查脚本加入定时任务(如每 5 分钟执行一次),当事务时长超过阈值(如 60 秒)时自动告警,避免问题扩大。
总结
MySQL 8.0 借助
performance_schema
提供了更强大的长事务监控能力,从 “查看活跃事务” 到 “溯源历史 SQL”,再到 “针对性处理”,形成了完整的排查链路。核心是:先通过基础 SQL 定位长事务线程,再通过开启历史监控溯源根因,最后结合业务场景处理。定期排查长事务,能有效避免锁等待、日志膨胀等性能问题,保障 MySQL 实例的稳定运行。