delete大表慢因逐行扫描加锁、写日志及不释放空间;truncate更快但需无外键、可弃自增id且不可回滚;分批删须基于索引单调字段,禁用limit offset。

DELETE 大表为什么慢得像卡住
因为 DELETE 是逐行扫描、逐行加锁、逐行写 binlog 和 undo log 的操作。哪怕你加了 WHERE id > 1000000,MySQL 仍可能全表扫描(尤其没走索引时),同时触发大量 MVCC 版本清理和缓冲池刷脏页。更糟的是,它不释放磁盘空间——删完表文件大小几乎不变。
- 常见错误现象:
DELETE FROM orders WHERE status = 'cancelled'跑了 20 分钟还没结束,SHOW PROCESSLIST显示Updating状态卡住 - 如果表有外键、触发器或全文索引,性能会进一步恶化
- 在主从架构下,大
DELETE还可能拖慢从库重放速度,造成延迟飙升
什么情况下直接用 TRUNCATE 更安全
TRUNCATE 是 DDL 操作,它重建表结构、清空数据页、重置自增计数器,不走事务、不写 row-based binlog(默认 statement 格式)、不触发触发器——所以快得多。但它不是万能的。
- 必须确保:没有外键引用该表;你不需要保留自增 ID 值;你接受它无法回滚(即使在事务里执行也立即生效)
- 注意兼容性:
TRUNCATE在 MySQL 8.0+ 支持TRUNCATE TABLE t PARTITION (p1),但低版本不支持分区级截断 - 权限要求比
DELETE高:需要DROP权限,而不仅是DELETE - 示例:
TRUNCATE TABLE logs_2023比DELETE FROM logs_2023快一个数量级,且立刻释放磁盘空间
分批 DELETE 的关键控制点
当不能用 TRUNCATE(比如要保留部分数据、有外键依赖、需按条件精准清理),分批是唯一靠谱方案。但“每次删 1000 行”这种拍脑袋做法常翻车。
- 必须基于**有索引的单调字段**分页,例如
id或create_time;避免用LIMIT offset, size,offset 越大越慢 - 推荐写法:
DELETE FROM events WHERE id ,然后循环更新边界值 - 每次执行后加
SLEEP(0.1)(应用层控制),防止长事务阻塞和 IOPS 打满 - 注意 autocommit:确保每批都在独立事务中提交,否则大事务会撑爆 undo log
- 监控
Innodb_row_lock_waits和Threads_running,发现锁等待飙升就减小单批 size
物理删除前绕不开的检查项
所谓“物理删除”,是指停写、备份、TRUNCATE 或 DROP 表后重建。这步省事,但漏掉检查就会丢数据或服务中断。
- 确认无活跃连接:
SELECT * FROM information_schema.PROCESSLIST WHERE INFO LIKE '%your_table%' - 检查复制状态:
SHOW SLAVE STATUS\G看Seconds_Behind_Master是否为 0,避免主库删完从库还在追 - 确认 binlog_format 是
STATEMENT(TRUNCATE才能被正确复制),否则切到ROW模式下TRUNCATE会被转成DELETE导致慢 - 备份不是“导出 SQL”,而是
mysqldump --single-transaction --no-create-info或直接拷贝.ibd文件(需innodb_file_per_table=ON)
实际执行时,最易被忽略的是 binlog 格式对 TRUNCATE 行为的影响,以及分批时没用索引字段做边界导致越删越慢。这两点不提前验证,删到一半才发现卡死,就得切回慢速 DELETE 或硬重启。











