DELETED表是SQL Server触发器中自动存在的逻辑表,仅在DELETE/UPDATE触发器内可见,存储被删行或更新前旧值,结构同原表,支持显式字段查询与JOIN,但无索引、不可用于INSERT触发器。

DELETED 表在 DELETE / UPDATE 触发器中自动存在
SQL Server 的 AFTER 或 INSTEAD OF 触发器里,DELETED 是一个内置的临时逻辑表,它不是真实物理表,也不需要声明或创建。只要触发器响应的是 DELETE 或 UPDATE 操作,SQL Server 就会自动生成并填充该表——DELETE 时存被删行的完整快照,UPDATE 时存更新前的旧值。
注意:它只在触发器作用域内可见,不能在外部查询;也不能在 INSERT 触发器中使用(此时只有 INSERTED)。
用 SELECT 从 DELETED 表读取原始数据最直接
最常见需求是记录删除/修改前的数据,比如写入审计日志。直接 SELECT 即可,但要注意字段必须显式列出(避免依赖顺序),且要处理 NULL 值兼容性:
CREATE TRIGGER tr_log_deleted_orders
ON orders
AFTER DELETE
AS
BEGIN
INSERT INTO order_audit (order_id, customer_id, amount, action, audit_time)
SELECT
d.order_id,
d.customer_id,
d.amount,
'DELETE',
GETDATE()
FROM DELETED d;
END;
-
DELETED表别名(如d)强烈建议加上,否则多表关联或子查询时容易歧义 - 不要写
SELECT * FROM DELETED—— 字段顺序、新增列、NULL 约束变化都可能让后续插入失败 - 如果原表有计算列、IDENTITY 或 FILESTREAM 字段,
DELETED中对应值仍为删除前的实际值(非表达式结果)
UPDATE 触发器中 DELETED 和 INSERTED 要区分使用
UPDATE 触发器同时有 DELETED(旧值)和 INSERTED(新值),常用于比对变更字段。例如只记录 price 变更:
CREATE TRIGGER tr_track_price_change
ON products
AFTER UPDATE
AS
BEGIN
INSERT INTO price_change_log (product_id, old_price, new_price, changed_at)
SELECT
d.product_id,
d.price,
i.price,
GETDATE()
FROM DELETED d
INNER JOIN INSERTED i ON d.product_id = i.product_id
WHERE d.price i.price OR (d.price IS NULL AND i.price IS NOT NULL) OR (d.price IS NOT NULL AND i.price IS NULL);
END;
-
WHERE子句必须显式处理 NULL 比较,因为NULL = NULL返回 UNKNOWN - 两个逻辑表结构完全一致,但内容不同;JOIN 条件通常用主键或唯一约束字段
- 若触发器是
INSTEAD OF UPDATE,则DELETED仍是原始值,但实际更新尚未发生,需手动执行UPDATE
DELETED 表没有索引,大数据量时性能敏感
DELETED(和 INSERTED)本质是内存中的行集,无索引、不可加约束、不支持统计信息。当单次操作影响数万行时,直接 JOIN 或子查询可能变慢:
- 避免在
DELETED上写复杂嵌套子查询或多次引用;优先用 CTE 或临时表缓存中间结果 - 如果只是记录日志,考虑异步方式(如 Service Broker 或应用层捕获),而非在触发器里做耗时 I/O
- 触发器中对
DELETED的任何操作都会延长事务锁持有时间,特别是涉及外键引用或远程表时
真正容易被忽略的是:触发器执行期间,DELETED 内容已固定,但若触发器本身又引发其他触发器(嵌套),下层触发器看到的 DELETED 是其自身操作对应的快照,不是原始顶层操作的 —— 这种链式上下文切换,调试时极易误判数据来源。










