Large Update触发Undo页竞争是因为多个事务并发申请同一undo segment header页,导致trx_rsegf_undo_seg_create等函数卡住;根本原因是innodb_undo_tablespaces设置过小(如2),使undo写入集中于少数文件,加剧header页锁争用。

为什么Large Update会触发Undo页竞争
不是UPDATE语句本身慢,而是它在写undo log时卡在trx_rsegf_undo_seg_create或trx_undo_assign_undo这类函数上——多个事务同时申请同一个undo segment header页,形成隐式串行化。尤其当innodb_undo_tablespaces设得太小(比如2),所有undo写入挤在1–2个文件里,header页锁争用直接拉高TPS抖动和锁等待。
怎么验证当前是否正被Undo页竞争拖慢
别只看SHOW ENGINE INNODB STATUS\G里的SEMAPHORES段。更直接的是查等待事件:
-
SELECT EVENT_NAME, COUNT_STAR, SUM_TIMER_WAIT FROM performance_schema.events_waits_summary_global_by_event_name WHERE EVENT_NAME LIKE 'wait/synch/mutex/innodb/undo_cur%'—— 如果undo_cur_mutex类计数远高于其他mutex,基本坐实 -
SELECT * FROM information_schema.INNODB_TRX WHERE TRX_ROWS_MODIFIED > 5000 AND TRX_STATE = 'RUNNING'—— 找出正在写大量undo的活跃事务 - 观察
Innodb_data_pending_fsyncs持续>10且Innodb_buffer_pool_wait_free>0,说明undo写入已压垮buffer pool和刷盘能力
真正有效的缓解手段:分块+独立表空间+调频
光靠SET GLOBAL innodb_purge_threads = 8没用,purge线程不参与undo分配竞争。关键三步必须一起做:
- 确保
innodb_undo_tablespaces≥ 36(MySQL 8.0.30+默认96,别改;5.7建议设为4~6)—— 这是分散undo segment header锁的唯一路径 - 把大UPDATE拆成主键范围分块,例如
UPDATE t SET x=1 WHERE id BETWEEN 10001 AND 15000,每批后COMMIT,避免事务跨段持有多个undo segment - 紧急时临时调小
innodb_purge_rseg_truncate_frequency到16(默认128),加快inactive回滚段释放,间接降低新事务抢segment header的概率
容易被忽略的隐性放大器
即使你分了块、调了参数,以下配置仍会让Undo页竞争雪上加霜:
-
innodb_undo_log_encrypt=ON:加密过程串行化undo page写入,CPU瓶颈下比IO瓶颈更致命 -
innodb_rollback_segments设得过低(如32以下):回滚段总数不够,多个事务被迫复用同一段,header锁冲突加剧 - 在事务里嵌套触发器:每个触发器执行都额外申请undo segment,但不随主事务提交立即释放,导致segment长期被占
Undo页竞争本质是资源争用问题,不是日志大小问题。监控要盯performance_schema里的mutex等待,调参要动innodb_undo_tablespaces和innodb_purge_rseg_truncate_frequency,而不能只盯着innodb_max_undo_log_size来回调。


















