delete千万行触发io抖动主因是高频刷脏页,而非删行数量;truncate可绕过该问题但受限于外键、权限及分区表支持;分批delete需控制批量大小、显式提交、休眠节流并确保索引命中。

为什么DELETE千万行会触发IO抖动
不是因为“删得多”,而是因为MySQL在DELETE过程中被迫高频刷脏页。每删一行,InnoDB都要标记记录为删除状态、更新二级索引、写undo日志和redo日志——这些操作最终都会让Buffer Pool里大量页面变成脏页。当后续查询或新写入需要内存空间时,MySQL就得把一批脏页flush到磁盘,而这个过程是同步阻塞的。尤其当innodb_buffer_pool_size设得大(比如 >1GB)、innodb_flush_neighbors又开着时,一次刷页可能连带刷掉相邻多个页,瞬间打满磁盘IO。
TRUNCATE能绕过IO抖动吗
能,但有条件。TRUNCATE本质是先DROP TABLE再重建,它不走Buffer Pool刷脏页流程,而是直接删.ibd文件、清空buffer pool中对应页的引用。所以不会触发常规的脏页刷盘链路。但它有两个硬限制:
- 必须确保表没被外键引用,否则报错
ERROR 1701 (HY000) - 需要
DROP权限,普通DELETE权限不够 - MySQL 5.7及以下版本对分区表不支持
TRUNCATE TABLE t PARTITION (p1),强行用会报错
如果只是清空整张表且满足上述条件,TRUNCATE是IO最友好的选择——实测500万行表,TRUNCATE耗时2秒,期间iostat -x 1看不到明显IO spike;而等量DELETE跑6分钟,期间磁盘await常超100ms。
分批DELETE时如何压制IO压力
关键不是“每次删多少”,而是控制刷盘节奏和锁粒度。单纯加LIMIT不够,还得配合应用层干预:
- 单批大小控制在500~1000行,避免事务过大撑爆undo log
- 每次
DELETE后显式COMMIT,确保autocommit开启或手动提交 - 应用层执行完一批后
SLEEP(0.1),给IO喘息时间,防止IOPS持续打满 - WHERE条件必须命中索引,否则会全表扫描+全表加锁,IO抖动更剧烈(常见错误:
DELETE FROM logs WHERE DATE(log_time) = '2023-01-01') - 监控
Innodb_buffer_pool_pages_dirty,如果持续高于总页数的70%,说明刷盘已跟不上,要减小单批量
真正危险的“优化”动作
有些方案看似快,实则埋雷:
- 临时关
innodb_flush_log_at_trx_commit=2:主库能提速,但从库重放binlog时可能因日志丢失导致数据不一致 - 删前禁用所有索引再重建:对在线表执行
ALTER TABLE ... DROP INDEX本身就会锁表,且重建索引期间IO压力不比DELETE小 - 用
ORDER BY id LIMIT但id不是单调递增字段:比如用created_at排序,一旦有并发插入,容易漏删或重复删
最稳妥的路径其实是:确认可TRUNCATE就直接上;不能TRUNCATE的,老实用带索引字段的分批DELETE + 应用层节流。别为了省几秒去碰那些破坏一致性边界的操作。











