TRUNCATE比DELETE快是因为它不删数据而是换表:TRUNCATE是DDL,直接释放原segment并新建空segment;DELETE是DML,需逐行加锁、写undo/redo日志、维护索引,性能随行数线性下降。

TRUNCATE 比 DELETE 快,不是因为“优化得好”,而是根本没走同一条路——它不删数据,直接换表。
TRUNCATE 是 DDL,DELETE 是 DML,执行链路完全不同
MySQL(InnoDB)里,TRUNCATE TABLE 被识别为 DDL 操作,内核调用的是 ha_innobase::truncate(),实际行为接近「释放原表 segment + 新建空 segment」;而 DELETE FROM table_name 即使不带 WHERE,仍是标准 DML,必须走完整行级处理链路:row_delete_for_mysql() → 逐行加 X 锁 → 打 delete bit → 写 undo log → 生成 binlog row event → 维护二级索引。
这意味着:
- 百万行表上,
DELETE= 百万次锁、百万次日志写入、百万次缓冲区刷盘;TRUNCATE= 一次页释放 + 一次元数据重置 -
TRUNCATE不扫描聚簇索引,不读任何数据页;DELETE必须遍历整棵 B+ 树 -
TRUNCATE的 redo log 只记 segment 重置和 FSEG_HEADER 变更;DELETE的 redo/undo 日志量随行数线性增长
TRUNCATE 真正释放磁盘空间,但依赖 innodb_file_per_table
很多人执行完 TRUNCATE 后发现 ls -lh table_name.ibd 文件大小没变,就以为“没生效”。关键看配置:
- 若
innodb_file_per_table = ON(MySQL 5.6+ 默认),TRUNCATE会把.ibd文件截断回初始大小(如 96KB),SELECT DATA_LENGTH FROM INFORMATION_SCHEMA.TABLES值也会骤降 - 若
innodb_file_per_table = OFF,数据仍在共享表空间ibdata1中,TRUNCATE无法释放操作系统级磁盘空间——此时只能导出 → DROP → 重建 → 导入,或升级到 MySQL 8.0+ 配合innodb_undo_log_truncate
TRUNCATE 在事务中会隐式提交,且权限要求不同
TRUNCATE 的“快”是以牺牲事务控制为代价的:
- 哪怕你写在
BEGIN和COMMIT之间,TRUNCATE执行那一刻,当前事务已自动提交;BEGIN; TRUNCATE TABLE t; ROLLBACK;中的ROLLBACK完全无效 -
TRUNCATE需要DROP权限(MySQL 中等价于ALTER),不是DELETE权限;报错ERROR 1142 (42000): TRUNCATE command denied就是缺这个 - 外键硬引用会直接报错:
Cannot truncate a table referenced in a foreign key constraint;临时解法是SET FOREIGN_KEY_CHECKS = 0,但务必配对恢复
TRUNCATE 重置 AUTO_INCREMENT,但不触发外键级联或触发器
这是开发最常踩的坑之一:
-
TRUNCATE直接修改内存中dict_table_t::autoinc字段,AUTO_INCREMENT一定重置为 1(或MIN(1, offset));DELETE不动该值,新插入仍从最大 ID +1 开始 - 若业务用自增 ID 作外部系统序列号,
TRUNCATE后首次插入可能撞上旧 ID,引发冲突;查当前值不能靠SHOW CREATE TABLE,得用SELECT AUTO_INCREMENT FROM INFORMATION_SCHEMA.TABLES -
TRUNCATE绕过存储引擎层的 DML 校验入口(如row_ins_check_foreign_constraints()),所以不触发ON DELETE CASCADE或BEFORE/AFTER DELETE触发器
真正要注意的,不是“哪个更快”,而是“它根本不是同一个东西”——TRUNCATE 的快,来自彻底跳过 InnoDB 行级引擎;一旦你依赖自增连续性、外键联动、触发器审计或事务原子性,它就不再是可选项。











