TRUNCATE快是因为它不走行级处理链路,而是释放旧段+新建空段;DELETE则逐行加锁、写日志、标记删除,性能差且不释放空间。

TRUNCATE为什么快:它根本不是在删数据
因为TRUNCATE不走InnoDB的行级处理链路。它不扫描聚簇索引、不加行锁、不写undo log、不生成redo log(除元数据变更外)、不触发触发器,也不维护MVCC版本链。本质是“释放旧段 + 新建空段”,整个过程绕开了DELETE所依赖的全部DML基础设施。
DELETE的执行路径:每行都是一次完整事务操作
DELETE在InnoDB中会逐行调用row_delete_for_mysql(),对每一行做:加X锁 → 标记delete bit → 写入undo log → 生成binlog事件 → 刷redo log缓冲区。百万行 = 百万次锁竞争 + 百万次日志落盘 + 百万次缓冲区管理。即使没WHERE,它也必须遍历全表。
- 锁类型是行锁+间隙锁(RR隔离级别下),并发写压力大时容易阻塞
- 磁盘空间不释放,只标记删除,后续由purge线程异步回收
-
AUTO_INCREMENT值保留在内存缓存中,下次插入仍从最大ID+1开始
TRUNCATE的真实行为:DDL级元数据重置
TRUNCATE调用的是ha_innobase::truncate(),内部执行的是存储引擎层的段级操作。它直接修改dict_table_t::autoinc字段重置自增计数器,并重建表空间元数据。是否真正释放操作系统文件空间,取决于innodb_file_per_table配置:
- 若
innodb_file_per_table=ON(MySQL 5.6+默认),.ibd文件会被截断回初始大小(如96KB) - 若
innodb_file_per_table=OFF,数据仍在共享表空间ibdata1中,TRUNCATE无法释放磁盘空间 - 外键约束检查被跳过,因为TRUNCATE不进入行级校验入口
row_ins_check_foreign_constraints()
最容易被忽略的生产陷阱
TRUNCATE不可回滚、不触发触发器、重置自增ID、需要DROP权限——这四点在自动化脚本或ORM封装里极易被掩盖。比如用query_builder.truncate('logs')替代delete().where('created_at ,表面看只是清日志,实则可能让下游系统因ID重复或缺失清理钩子而异常。真正的危险从来不在命令本身,而在它绕过的那些“默认保障”。











