批量insert产生大量脏页,因每行修改主键页、二级索引页、undo页等,单条可生成2–5个脏页;若buffer pool不足或innodb_max_dirty_pages_pct过高(默认90),易触发同步刷盘导致阻塞。

批量插入时,Buffer Pool 不会立刻刷脏页,而是把所有修改先攒在内存里,等后台线程或触发条件满足时再统一写盘——但攒太多就会卡住你。
为什么批量 INSERT 会产生大量脏页
每次插入新行,InnoDB 实际上要操作多个页:主键索引页、二级索引页、Undo 页(用于回滚)、甚至可能触发页分裂。这些页一旦被修改,就变成脏页,进入 Flush List 链表。关键点在于:INSERT 不像 UPDATE 那样只改已有页,它经常要分配新页、填充数据、更新父节点指针——这些全是脏页来源。
- 单条
INSERT可能生成 2–5 个脏页(尤其带多个二级索引时) - 用
INSERT ... VALUES (...), (...), (...)批量写入,事务内所有页修改都集中在同一时刻标记为脏 - 如果
innodb_buffer_pool_size不够大,很快就会耗尽Free List,被迫从LRU List淘汰页——淘汰前还得先刷脏,进一步加剧刷新压力
page_cleaner 线程怎么处理这批脏页
MySQL 启动后默认启用 innodb_page_cleaners(通常等于 CPU 核数),它们轮询 Flush List,按策略把脏页刷到磁盘。但这个过程不是匀速的:
- 当
Innodb_buffer_pool_pages_dirty占总页数比例超过innodb_max_dirty_pages_pct(默认 90%),cleaner 会加速;超过阈值太多,前台线程会直接参与同步刷盘(sync flush),表现为Updating或Writing to net卡住 -
innodb_adaptive_flushing = ON(MySQL 8.0+ 默认)会让 cleaner 动态调整节奏,但在大批量插入场景下反而容易抖动:一会儿猛刷、一会儿停顿,导致 I/O 波动和延迟毛刺 - 真正决定刷多快的底层参数是
innodb_io_capacity和innodb_io_capacity_max—— 它们告诉 cleaner “你最多能发多少 I/O 请求”,设得太低(如默认 200)会导致脏页积压,设得太高又可能打爆磁盘队列
哪些参数和写法会显著影响脏页堆积速度
不是所有批量插入都一样慢。以下配置和写法差异,直接决定脏页是“平稳流入”还是“瞬间洪峰”:
-
innodb_flush_log_at_trx_commit = 1是必须的,别为了快改成 2——否则 crash 后可能丢整个批次的数据 - 避免单事务插入超 500 行;每批控制在 100–300 行(视行大小和索引数量调整),并显式
COMMIT,让脏页分批进入Flush List,给 cleaner 追赶时间 -
innodb_max_dirty_pages_pct = 75比默认 90 更安全:留出缓冲空间,避免临界点突增同步刷盘概率 - 用
LOAD DATA INFILE替代多值INSERT:它绕过部分 SQL 层解析,脏页生成更紧凑,且支持DISABLE KEYS减少二级索引维护开销
怎么确认脏页真的没堵住
别只盯着 SHOW ENGINE INNODB STATUS 里的“Buffer pool hit rate”,那反映的是读缓存效率。真正要看的是脏页压力指标:
-
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_wait_free':非零说明频繁缺空闲页,需扩容或降负载 -
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_dirty':持续高于总页数的 10% 就该预警(比如 10GB buffer pool 对应约 65 万页,脏页 > 6.5 万页就危险) -
SHOW GLOBAL STATUS LIKE 'Innodb_log_waits':非零代表 redo log 写满等待,本质是刷盘跟不上,必须调innodb_io_capacity或拆批
最隐蔽的坑是:你以为调大了 innodb_buffer_pool_size 就万事大吉,但若 innodb_io_capacity 还卡在默认值,buffer pool 只是把脏页“堆得更高”,而不是“流得更顺”。











