TRUNCATE快是因为它本质是“换表”而非删数据:释放原表段并新建空段,跳过行级锁、undo log、redo log等DML开销,仅记一条DDL日志,不触发触发器,且隐式提交、不可回滚。

TRUNCATE 快,是因为它根本不是在“删数据”,而是在“换表”——释放段、重置元数据、跳过整套 InnoDB 行级处理链路。
TRUNCATE 绕开了 InnoDB 的行级日志路径
DELETE 是标准 DML:InnoDB 会启动事务,逐行遍历聚簇索引,对每行加 X 锁、标记 delete bit、写 undo log、再按 binlog 格式生成日志条目。百万行 = 百万次锁 + 百万次日志写入 + 百万次缓冲区刷盘。
TRUNCATE 是 DDL 操作,调用 ha_innobase::truncate(),内部执行的是「释放原表 segment + 新建空 segment」。它不走 row_delete_for_mysql() 流程,因此:
- 不扫描任何数据页,不访问 B+ 树
- 不生成 undo log(所以不可回滚)
- 不写 redo log 缓冲池(
log_buffer) - binlog 只记一条
TRUNCATE TABLE tDDL 事件 - 不触发
BEFORE/AFTER DELETE触发器
TRUNCATE 真正释放磁盘空间的前提是 innodb_file_per_table=ON
很多人执行完 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 TABLE→ 重建 → 导入,或升级到 MySQL 8.0+ 并启用innodb_undo_log_truncate配合 purge
TRUNCATE 会重置 AUTO_INCREMENT,但不检查外键约束
这是开发最常踩的坑之一:
- TRUNCATE 直接修改内存中表元数据结构
dict_table_t::autoinc字段,所以AUTO_INCREMENT一定重置为 1(或MIN(1, offset)) - 而 DELETE 只操作数据行,不动表定义缓存,插入新行时 ID 仍从上次最大值 +1 继续
- 若业务依赖自增 ID 单调递增(比如作为外部系统序列号),TRUNCATE 后首次插入可能撞上旧 ID
- 外键检查被跳过,是因为 TRUNCATE 绕过了存储引擎层的 DML 校验入口(如
row_ins_check_foreign_constraints()),属于 DDL 级元数据操作
TRUNCATE 在事务中会隐式提交,且权限要求不同
哪怕你把它写在 BEGIN 和 COMMIT 之间,TRUNCATE 执行那一刻,当前事务就已提交 —— 这不是 bug,是 MySQL 的明确行为。
权限方面也容易忽略:
- TRUNCATE 实际需要
DROP权限(不是DELETE权限),否则报错ERROR 1142 (42000): TRUNCATE command denied to user - 有外键引用该表时,直接报错
ERROR 1701 (HY000): Cannot truncate a table referenced in a foreign key constraint;临时解法是SET FOREIGN_KEY_CHECKS = 0,但必须确认下游无级联逻辑 - 临时表在 MySQL 8.0+ 支持 TRUNCATE,但低版本不支持,执行会报错
真正要注意的不是“哪个快”,而是“它快得彻底甩开 DML 路径”——这意味着所有依赖行级语义的功能(回滚、触发器、条件过滤、外键校验、binlog 行格式还原)全都不适用。用之前,先确认你的场景真的只需要“清空+重置”,而不是“删除+留痕”。


















