直接delete大表极危险,因每行需写undo日志、更新所有二级索引、持行锁;应分批删除(where+order by+limit+显式事务)、用分区表drop partition或rename交换表,避免锁表与日志膨胀。

直接在大表上执行 DELETE 是最常见也最危险的负载来源——它不光慢,还会拖垮整个数据库。真正有效的优化不是“让 DELETE 变快”,而是绕开它的底层开销:undo 日志、索引维护、行锁堆积和全表扫描。
为什么 DELETE 会引发高负载?
MySQL 的 DELETE 是 DML 操作,每删一行都得记 undo log、更新所有相关索引、持有行级锁。千万级表上执行 DELETE FROM logs WHERE create_time ,可能触发:
- 全表扫描(WHERE 字段无索引时)
- 事务日志暴涨,填满
ib_logfile -
innodb_row_lock_waits持续飙升,阻塞其他写入 - 主从延迟跳涨,尤其在 binlog 格式为
STATEMENT时
分批删除必须带 ORDER BY + LIMIT
只写 LIMIT 10000 不够,没 ORDER BY 会导致每次删的行随机,无法保证范围连续,后续无法推进。更糟的是,MySQL 可能先扫全表找匹配行,再取前 N 条——等于白加 LIMIT。
正确写法:
DELETE FROM orders WHERE id BETWEEN 1000001 AND 1100000 LIMIT 10000;
或(有时间字段且已建索引):
DELETE FROM logs WHERE create_time <p>关键点:</p>
- 优先用主键或有索引的单调字段(如
id、create_time)做范围切分 - 绝对不用
LIMIT 10000 OFFSET 100000:OFFSET 越大越慢,本质是跳过前 N 行再扫 - 每次执行后查
SELECT MAX(id) FROM ...获取下一批起点,别依赖自增步长
分区表删除优先用 DROP PARTITION
如果表按时间做了 RANGE 或 LIST 分区(比如按年/月分),DROP PARTITION 是秒级操作,不走 DML 流程,也不生成 undo 日志。
确认分区定义:
SHOW CREATE TABLE logs;
然后直接删整块:
ALTER TABLE logs DROP PARTITION p2022;
注意:
- 该操作不可回滚,但比
DELETE安全——它只是删数据文件,不碰 buffer pool - 不能和
WHERE混用;若只需删某分区中部分数据,先EXCHANGE PARTITION导出子集再处理 - MySQL 5.7+ 支持,但分区字段必须是整型或可转为整型的表达式(如
YEAR(create_time))
删大部分数据时,用 TRUNCATE + INSERT 替代
当你要保留不到 20% 的数据(例如只留最近 90 天),DELETE 就是错的选择。新建表导入再切换,I/O 和锁开销几乎为零。
步骤:
CREATE TABLE orders_new LIKE orders;<br>INSERT INTO orders_new SELECT * FROM orders WHERE create_time >= '2026-04-01';<br>RENAME TABLE orders TO orders_old, orders_new TO orders;<br>DROP TABLE orders_old;
风险点:
- 期间需停写,或应用层加双写开关——否则新旧表状态不一致
- 磁盘空间至少要够存两份数据;若不够,先清理归档再操作
-
TRUNCATE会重置AUTO_INCREMENT,而此方案不会
真正容易被忽略的,是“删完之后”的事情:没清空的 undo 表空间不会自动收缩,ibdata1 可能持续膨胀;分区表删完后记得 OPTIMIZE TABLE 回收碎片——这些不处理,下次照样卡。











