MySQL 8.0 长事务排查

在 MySQL 数据库运维中,长事务是常见的性能 “隐形杀手”—— 它可能导致锁等待堆积、事务日志(undo/redo log)膨胀、甚至拖慢整个实例的并发能力。尤其是 MySQL 8.0 版本对事务监控机制做了优化,借助performance_schemasys库,我们能更精准地定位长事务问题。本文将从 “为什么关注长事务” 出发,逐步讲解如何查看活跃长事务、溯源历史执行语句,并给出实用的处理建议。

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 个核心字段:
 
  1. mysql_thread_id:线程 ID,后续终止事务需用它(如KILL 3062872)。
  2. Command = Sleep:这是最需要警惕的状态!Sleep表示线程处于 “空闲但未释放事务” 状态 —— 通常是业务代码未及时执行COMMIT/ROLLBACK,导致事务挂起(默认超时由interactive_timeout控制,默认 8 小时)。
  3. latest_statement = NULL:Sleep状态的事务无 “当前执行语句”,说明它已停止 SQL 执行,但事务未结束,属于典型的 “遗忘事务”。
 
而第二条Command = Query的事务仅运行 309 微秒(极短),且语句是我们当前的查询语句,属于正常操作,无需关注。

3. 进阶操作:查看长事务的历史执行语句

基础查询可能遇到 “latest_statement 为 NULL” 的情况(如 Sleep 事务),此时无法直接看到事务之前执行过的 SQL。MySQL 8.0 可通过开启performance_schema的 “历史语句记录” 功能,溯源长事务的完整执行过程。

3.1 第一步:开启历史语句监控

默认情况下,performance_schemaevents_statements_history_longevents_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 操作(如BEGINSELECT * FROM big_tableUPDATE等),从而定位到导致事务变长的具体业务 SQL。

4. 长事务的常见问题与处理建议

排查出长事务后,需结合业务场景处理,避免盲目终止事务:

4.1 区分 “正常长事务” 与 “异常长事务”

  • 正常长事务:业务必需的操作(如大表全量同步、数据归档),通常在低峰期执行,且有明确的超时控制。这类事务无需处理,只需监控其执行进度。
  • 异常长事务:多为代码 bug 导致(如未提交事务、死循环)或慢查询引发(如无索引的大表查询)。这类事务需优先处理。

4.2 处理异常长事务的步骤

  1. 记录现场:先执行SHOW ENGINE INNODB STATUS\G,保存事务的锁信息、回滚进度,便于后续分析。
  2. 通知业务:确认事务对应的业务模块(通过UserSQL_TEXT判断),通知开发人员评估终止事务的影响。
  3. 终止事务:若确认无影响,执行KILL [mysql_thread_id](如KILL 3062872),MySQL 会自动回滚该事务(回滚时间取决于事务大小,大事务需耐心等待)。
  4. 优化根源:修复代码 bug(如确保事务结束后执行COMMIT)、优化慢查询(如添加索引)、缩短事务范围(如拆分大事务为小事务)。

5. 注意事项

  1. performance_schema 的性能开销:开启过多setup_consumers(如events_statements_history_long)会增加 CPU 和内存开销,生产环境建议仅开启必要的监控项,且定期清理历史数据。
  2. 参数调优:若频繁出现 Sleep 状态的长事务,可适当减小interactive_timeoutwait_timeout(如设为 300 秒),让空闲事务自动释放,但需提前与业务确认(避免影响长连接业务)。
  3. 定期监控:将长事务排查脚本加入定时任务(如每 5 分钟执行一次),当事务时长超过阈值(如 60 秒)时自动告警,避免问题扩大。

总结

MySQL 8.0 借助performance_schema提供了更强大的长事务监控能力,从 “查看活跃事务” 到 “溯源历史 SQL”,再到 “针对性处理”,形成了完整的排查链路。核心是:先通过基础 SQL 定位长事务线程,再通过开启历史监控溯源根因,最后结合业务场景处理。定期排查长事务,能有效避免锁等待、日志膨胀等性能问题,保障 MySQL 实例的稳定运行。

posted on 2025-10-15 09:24  数据与人文  阅读(23)  评论(0)    收藏  举报