truncate比delete快因直接释放数据页且日志少、锁粒度小、重置hwm;但受限于外键、触发器、权限等条件,且会重置identity列。

TRUNCATE 比 DELETE 快,但不能无条件替换——必须确认表没被外键引用、没触发器依赖、且你不需要回滚能力。
TRUNCATE 为什么比 DELETE 快
根本区别在底层操作方式:TRUNCATE 不逐行删除,而是直接释放数据页(data pages),只在事务日志里记下“哪些页被回收了”;而 DELETE 对每一行都生成一条日志记录,并加行锁。当表有上百万行时,DELETE 的日志膨胀和锁竞争会明显拖慢执行。
实测常见场景下,TRUNCATE TABLE orders 耗时通常是 DELETE FROM orders 的 1/10 到 1/5,尤其在高并发或日志空间受限的环境中差距更明显。
- 日志写入量:TRUNCATE 可能只写几十 KB 日志;DELETE 百万行可能写几百 MB
- 锁粒度:TRUNCATE 通常只申请表级锁(
SCH_M);DELETE 默认是行锁,容易升级为页锁甚至表锁 - 高水线(HWM):TRUNCATE 后 HWM 回到起始位置,后续
SELECT *全表扫描更快;DELETE 不动 HWM,扫描仍要跳过大量空块
哪些情况 TRUNCATE 会直接报错
遇到以下任一条件,TRUNCATE TABLE 会立即失败,不是慢,是根本不能执行:
- 表被其他表的外键(
FOREIGN KEY)引用(哪怕只是定义存在,不启用也报错) - 表参与了分区方案但
WITH (PARTITIONS(...))用法不合法(例如指定不存在的分区号) - 表是视图、临时表(
#temp)、表变量(@table)或内存优化表(MEMORY_OPTIMIZED = ON) - 当前用户没有
ALTER权限(TRUNCATE 是 DDL,不是 DML)
典型错误信息:Msg 4712, Level 16, State 1: Cannot truncate table 'orders' because it is being referenced by a FOREIGN KEY constraint.
TRUNCATE 后标识列(IDENTITY)重置是特性,不是 bug
如果表有 IDENTITY 列,TRUNCATE 一定会把它重置为种子值(如 1);而 DELETE 保留上次最大值。这在某些业务中是关键差异:
- 需要连续编号(如单据流水号)→ TRUNCATE 合适
- 编号需全局唯一且不可重复(如订单 ID)→ 不能用 TRUNCATE,必须用 DELETE 或显式
DBCC CHECKIDENT(..., RESEED) - 想清空数据但保留自增状态 → 只能用
DELETE,或 TRUNCATE 后手动DBCC CHECKIDENT('t', RESEED, @last_value)
注意:DBCC CHECKIDENT 本身不校验数据,只改元数据;若表非空就执行 RESEED,可能导致后续插入主键冲突。
分区表上 TRUNCATE 要特别小心 WITH PARTITIONS
SQL Server 2016+ 支持按分区截断,语法是 TRUNCATE TABLE t WITH (PARTITIONS (2, 4, 6 TO 8))。但几个硬约束常被忽略:
- 表必须已分区,且索引与表对齐(aligned);否则报错
Msg 4901 -
PARTITIONS子句里不能混用表达式和字面量,比如WITH (PARTITIONS (2, @p))会报错 —— 参数化不支持 - 指定分区号超出实际范围(如表只有 5 个分区却写
6 TO 8)会失败,不会静默跳过 - 即使只截一个分区,整个语句仍是原子操作:要么全成功,要么全回滚(但无法像 DELETE 那样部分提交)
验证分区数可用:SELECT $PARTITION.pf_name(id) AS partition_number FROM sys.partitions WHERE object_id = OBJECT_ID('t'),别靠记忆或文档猜。
真正麻烦的不是语法写错,而是误判依赖关系——比如开发库没外键,上线库加了但忘了同步检查;或者以为视图不影响,其实物化视图(indexed view)会阻止 TRUNCATE。动手前,先跑一遍 SELECT OBJECT_NAME(parent_object_id), type_desc FROM sys.foreign_keys WHERE referenced_object_id = OBJECT_ID('your_table') 看有没有隐藏引用。










