truncate快是因为它不删数据而是换表:释放原表segment并新建空segment,跳过扫描b+树、行锁、undo/redo日志及触发器,仅记一条ddl日志;而delete是逐行dml操作,开销随行数线性增长。

TRUNCATE 为什么快:它根本不是删数据,而是换表
TRUNCATE 快,是因为它不走 InnoDB 的行级删除流程。它调用 ha_innobase::truncate(),直接释放原表的 segment(段),新建一个空 segment,跳过所有 DML 开销:不扫描 B+ 树、不加行锁、不写 undo log、不刷 redo log buffer,binlog 里只记一条 TRUNCATE TABLE t DDL 事件。
而 DELETE FROM t 是标准 DML:启动事务、逐行遍历聚簇索引、每行加 X 锁、标记 delete bit、生成 undo log 和 binlog 条目——百万行 = 百万次锁 + 百万次日志写入 + 百万次缓冲区刷盘,极易卡住主从同步、撑爆 undo 表空间。
生产环境用 TRUNCATE 的硬性前提:必须满足这三点
不是所有表都能直接上 TRUNCATE,漏查一项就报错或埋坑:
-
外键约束存在? —— 查
INFORMATION_SCHEMA.KEY_COLUMN_USAGE,若有结果,TRUNCATE直接报ERROR 1701;必须先DROP FOREIGN KEY或改用DELETE -
权限够吗? —— 需要
DROP权限,不是DELETE权限就够了;线上账号常被限制,一执行就报ERROR 1142 -
innodb_file_per_table是否为 ON? —— 默认是 ON(MySQL 5.6+),TRUNCATE后.ibd文件会截断回初始大小(如 96KB);若为 OFF,空间仍留在ibdata1里,TRUNCATE看似成功,ls -lh却发现文件大小没变
TRUNCATE 不可回滚,但“隐式提交”才是真坑点
很多人以为只要包在 BEGIN / COMMIT 里就能控制原子性,其实不行:TRUNCATE 执行那一刻,当前事务就已隐式提交——这不是 bug,是 MySQL 明确行为。哪怕你前面刚 INSERT 几百条数据,TRUNCATE 一跑,那些 INSERT 就再也 ROLLBACK 不回去了。
另外两个关键副作用必须确认:
-
AUTO_INCREMENT一定重置为 1(或MIN(1, offset)),如果业务把自增 ID 当作外部序列号用,首次插入可能撞上旧 ID - 完全不触发
BEFORE/AFTER DELETE触发器——如果依赖触发器发消息、写审计日志、更新统计表,TRUNCATE会静默跳过
DELETE 分批删也救不了大表清空场景
有人想“安全一点”,把 DELETE FROM t 改成 DELETE FROM t LIMIT 1000 循环执行。这确实能缓解锁和日志压力,但本质仍是 DML:每批都写 undo/redo/binlog、每批都走事务、每批都可能触发触发器、自增值不重置、表空间不收缩。
真正的大表清空(比如日志表、临时中间表),只要满足无外键、有 DROP 权限、innodb_file_per_table=ON,TRUNCATE 就是唯一合理选择。其他方案都是在给系统徒增负担。
最容易被忽略的是 innodb_file_per_table 设置和外键检查——这两项不提前验证,TRUNCATE 在生产环境要么失败,要么“成功但没释放空间”,后者更危险。











