TRUNCATE快是因为它不删数据行而是重建表:重命名原表、新建空表、删除旧文件,全程不扫描、不加行锁、不写undo日志,仅记一条DDL日志;DELETE则逐行处理,生成大量日志并维护MVCC。

TRUNCATE 快,是因为它根本不删数据行,而是换掉整张表的物理存储
你执行 TRUNCATE TABLE orders 时,InnoDB 实际干的是:把原表重命名(比如变成 #sql-xxx),再用空结构重建一张同名新表,最后删掉旧表文件。整个过程不扫描任何行、不加行锁、不写 undo log,只记一条 DDL 日志。而 DELETE FROM orders 是逐行遍历、每行都生成 undo 和 redo、更新索引、维护 MVCC 版本链——100 万行就要做 100 万次 I/O 和日志写入。
MySQL 中 TRUNCATE 能回滚?别信文档直觉
MySQL InnoDB 的 TRUNCATE 在显式事务中确实能回滚,但这不是标准行为,是 InnoDB 自己实现的“伪事务”:它把操作拆成 RENAME + DROP + CREATE 三步,并包裹在事务内。一旦 ROLLBACK,就恢复原表名、撤回建表动作。但注意:
- 这个能力仅限 MySQL InnoDB;PostgreSQL 和 Oracle 的
TRUNCATE虽也支持回滚,但机制不同,且 PostgreSQL 8.4+ 才加入该特性 - 如果事务中混用了
TRUNCATE和其他 DML,回滚可能失败或行为异常 - 从 binlog 角度看,
TRUNCATE记录的是 DDL 事件,主从复制时从库执行的是等效重建,不是“反向删除”
DELETE 回滚快?其实只是“看起来快”
DELETE 回滚看似简单(把 xmax 改回去就行),但在大表上它并不轻量:
- 回滚要重放所有被删行的前镜像,undo log 可能已刷盘,需要随机读取
- 如果事务中途崩溃,恢复阶段必须重放 undo,耗时随删除行数线性增长
- PostgreSQL 中,
DELETE留下的死元组仍占空间,回滚后也不会自动释放,得靠后续VACUUM - MySQL 中,回滚虽快,但长事务期间会持续占用 undo 表空间,可能触发
innodb_undo_log_truncate或撑爆磁盘
外键、触发器、自增 ID 这些地方最容易翻车
选错命令的代价往往不在速度,而在副作用:
-
TRUNCATE遇到外键引用直接报错:ERROR 1701 (HY000): Cannot truncate a table referenced in a foreign key constraint,而DELETE只要满足约束就能过 -
TRUNCATE完全绕过触发器,审计日志、缓存清理这类逻辑全部失效 -
TRUNCATE会重置AUTO_INCREMENT值为 1(或MIN(1, offset)),DELETE不动它——如果你的业务拿 ID 当外部序列号,TRUNCATE 后插入可能撞 ID - 权限上,
TRUNCATE需要DROP权限(MySQL),不是只有DELETE权限就够的
真正关键的判断点从来不是“哪个快”,而是“我删完之后,系统状态是否还符合预期”。比如清空日志表,你以为只是删数据,但忘了下游有物化视图依赖它的变更日志,或者 Flink 作业正按 binlog 位点消费——这时候快没用,稳才重要。

















