长事务通过阻塞purge线程清理oldest_modification,导致flush_list脏页滞留,使脏页占比攀升、wait_free上涨,并引发log延迟与history list增长;需结合trx_weight和data_locks定位高权重长事务,调优时应匹配磁盘IOPS并启用自适应刷盘。

长事务如何让Buffer Pool脏页比例失控
长事务本身不直接写Buffer Pool,但它会阻止已修改页(脏页)被刷盘——因为InnoDB必须保留这些页的旧版本用于MVCC一致性读。只要事务没提交,对应页的oldest_modification就无法被purge线程清理,flush_list上的脏页就卡住不动。
后果很直接:脏页占比持续攀升,innodb_buffer_pool_pages_dirty / innodb_buffer_pool_pages_total可能逼近80%甚至更高;同时innodb_buffer_pool_wait_free开始上涨,说明后台刷盘跟不上,新页申请被迫等待。
- 典型现象:
SHOW ENGINE INNODB STATUS里LOG段显示log sequence number和log flushed up to差值巨大(>1GB),且History list length持续增长 - 监控建议:用
information_schema.INNODB_METRICS查buffer_pool_dirty_pages和buffer_pool_flushed_pages每秒增量,比静态比例更能反映压力趋势 - 注意:脏页比例高 ≠ 一定危险,关键看
innodb_max_dirty_pages_pct_lwm是否被频繁触发,以及innodb_io_capacity是否匹配磁盘实际IOPS
怎么实时抓出正在拖慢刷盘的长事务
不能只看INFORMATION_SCHEMA.INNODB_TRX里的trx_started时间,得结合它对flush_list的实际阻塞效果。重点看两个字段:trx_mysql_thread_id和trx_weight(权重越高,涉及修改页越多)。
更实用的方法是关联performance_schema.data_locks和INNODB_TRX,过滤出持有RECORD锁且trx_state = 'RUNNING'、trx_started < NOW() - INTERVAL 60 SECOND的事务:
SELECT t.trx_id, t.trx_mysql_thread_id, t.trx_started,
t.trx_weight, l.OBJECT_SCHEMA, l.OBJECT_NAME, l.INDEX_NAME
FROM information_schema.INNODB_TRX t
JOIN performance_schema.data_locks l ON t.trx_id = l.TRANSACTION_ID
WHERE t.trx_state = 'RUNNING'
AND t.trx_started < NOW() - INTERVAL 60 SECOND
AND l.LOCK_DATA IS NOT NULL
ORDER BY t.trx_weight DESC LIMIT 5;- 如果
trx_weight> 10000,基本可判定该事务已修改大量页,且未提交,正在钉住flush_list - 避免用
SELECT * FROM INNODB_TRX全表扫——在大负载下会加剧锁竞争,优先走带索引的条件过滤 - MySQL 8.0.29+ 可直接查
performance_schema.events_transactions_current,字段更全,但需提前开启transaction消费者
调整刷盘策略前先确认磁盘真实吞吐能力
innodb_io_capacity不是调得越高越好。设为2000,但磁盘实际随机写IOPS只有800,只会导致大量os_log_pending_fsyncs堆积和log write pending超时。
验证方法很简单:用fio跑一次4K随机写压测,参数贴近InnoDB刷脏页模式:
fio --name=randwrite --ioengine=libaio --iodepth=64 --rw=randwrite \
--bs=16k --direct=1 --size=2G --runtime=60 --time_based \
--filename=/var/lib/mysql/ibdata1- 结果中重点关注
iops和lat (ns):若平均延迟超过10ms,说明磁盘已成瓶颈,innodb_io_capacity应设为实测IOPS × 0.7 -
innodb_max_dirty_pages_pct建议从75逐步下调到60,配合innodb_adaptive_flushing = ON,让InnoDB根据LSN增长速率动态调节刷盘节奏 - 切勿关闭
innodb_adaptive_flushing:它能防止“突发大事务提交后刷盘雪崩”,关了反而更容易触发checkpoint age too high告警
为什么杀掉长事务后脏页比例仍降得慢
事务回滚(或提交)只是释放flush_list上页的“不可刷”状态,并不等于立刻刷盘。InnoDB刷脏页由后台线程异步执行,受innodb_io_capacity、innodb_max_dirty_pages_pct_lwm和当前LSN推进速度三重控制。
常见误解是“一杀就降”,实际观察发现:脏页数下降滞后10–30秒很常见,尤其当innodb_buffer_pool_size > 20GB时,后台刷盘线程需要逐批调度页,且要避开用户查询热点页。
- 紧急情况下可用
SET GLOBAL innodb_max_dirty_pages_pct = 30强制加速,但会显著增加I/O毛刺,仅限临时救火 - 真正有效的长期解法是限制应用层事务粒度:单个事务修改行数超过1000行,就拆成多批次,每批后显式
COMMIT - 最容易被忽略的一点:
innodb_log_file_size过小(如默认48MB)会导致频繁checkpoint,放大长事务影响——建议设为至少1GB,且确保innodb_log_files_in_group × innodb_log_file_size ≥ 2GB


















