SQL Server 触发器中的 deleted 表如何使用

Alice
Alice
发布时间:2026-08-23| 163人浏览过
原创

前言

在 SQL Server 触发器里,`deleted` 是个很好用但也很容易被误解的对象:它不是实体表,不能脱离触发器存在,到了批量更新和软删除场景更不能按“普通表思路”去写。下面从出现条件、联表规则到归档恢复逐段拆解,帮你判断哪些写法只是碰巧可用,哪些才是能扛住生产环境的实现。

很多人第一次在 SQL Server 触发器里见到 deleted,都会把它当成一张“系统自动生成的表”来理解,结果一到批量更新、软删除或数据恢复场景就容易踩坑。本文按触发器生命周期、联表方式和归档恢复三个层面拆开讲,帮助你判断 deleted 到底能做什么、不能做什么,以及哪些写法在生产环境里更稳。

deleted 表什么时候存在,什么时候一定不要查

deleted 并不是真实存在的表,而是 SQL Server 在特定触发器执行期间提供的一份内存临时快照。它不能被 CREATE 或 DROP,也不能跨触发器访问;一旦当前事务结束,这份数据就会被销毁。

展示 deleted 表在不同触发器类型中的存在条件,以及 DELETE、UPDATE、INSERT 三种操作下的数据含义差异。
deleted 表的出现时机与数据范围用触发器类型和操作场景拆开看,能更快判断 deleted。

只要触发器类型是 AFTER DELETE、AFTER UPDATE 或者 INSTEAD OF DELETE/UPDATE,SQL Server 就会创建这个临时结构,其中保存的是操作发生前的原始行。

有一个非常常见的误区:在 AFTER INSERT 触发器里查询 deleted。这时结果为空,因为 INSERT 并不涉及旧数据,deleted 自然不会有内容。

两类操作里,deleted 存的内容并不一样

  • DELETE 操作:deleted 包含全部待删行,可直接用于归档、审计或删除前校验。
  • UPDATE 操作:deleted 只保存更新前的旧值,通常要和 inserted 联查才能判断字段变更。
  • 触发器外部:不要在普通查询环境里直接 SELECT deleted,否则会报“对象名 'deleted' 无效”。

为什么 deleted 和 inserted 联查必须靠主键

在 UPDATE 触发器中,最常见的需求是对比更新前后的字段值。这时很多人会想当然地认为 deleted 和 inserted 行序一致,于是写出类似 SELECT TOP 1 FROM deleted 配合 ORDER BY (SELECT NULL) 的代码,试图“取同一行”的旧值和新值。

展示 deleted 与 inserted 在 UPDATE 触发器中必须通过主键或复合唯一键联查,不能依赖行序。
联查 deleted 的正确方式批量更新时最容易出错的不是语法,而是把两张伪表按顺序硬配对。

这类写法不可靠。SQL Server 不保证两张伪表的行序天然一致,尤其是在批量操作时,依赖顺序几乎等于埋雷。

正确做法是始终使用主键或唯一键进行 INNER JOIN:

SELECT d.Id, d.Email AS old_email, i.Email AS new_email
FROM deleted d
INNER JOIN inserted i ON d.Id = i.Id;

联表时需要注意的三个细节

  • 如果主键是 (OrderId, LineNo) 这样的复合键,ON 条件必须写全:ON d.OrderId = i.OrderId AND d.LineNo = i.LineNo。
  • 避免直接写 SELECT *。宽表遇到大批量更新时,读取全部列会明显拖慢触发器执行。
  • 不要用 LEFT JOIN deleted 去“判断是否删除”。在 AFTER UPDATE 触发器里,deleted 本来就一定非空。

做软删除时,为什么要用 INSTEAD OF DELETE

如果你的目标不是把数据真的删掉,而是实现逻辑删除与后续恢复,那么关键点并不是“能不能读到 deleted”,而是“必须阻止物理删除真正发生”。

展示软删除场景下 INSTEAD OF DELETE、归档表、恢复操作之间的关系,以及 SET NOCOUNT ON、归档字段和索引控制等配套要点。
软删除、归档与恢复的关系恢复能力来自归档表,而不是 deleted 本身;软删设计是否稳定,关键看归档链路是否完整。

因此,这类设计应优先使用 INSTEAD OF DELETE。如果仍然使用普通删除后触发的逻辑,触发器执行时原表行已经进入物理删除流程,虽然 deleted 里还能看到旧值,但它不能代替稳定的软删方案,碰到并发事务时风险更高。

软删触发器的配套设计

  • 原表需要有 IsDeleted BIT DEFAULT 0 字段,用来承载逻辑删除状态。
  • 归档表建议放在独立数据库,至少也要放在独立文件组,避免主库故障时连归档一起丢失。
  • 触发器开头必须写 SET NOCOUNT ON,否则某些 ORM(如 Entity Framework)可能把归档 INSERT 的影响行数误判成主语句结果并抛异常。
  • 不要在触发器里调用另一个可能再次触发当前逻辑的存储过程,否则很容易出现嵌套调用甚至死循环。

deleted 不能直接恢复数据,真正能恢复的是归档

deleted 的生命周期只存在于当前触发器作用域内,事务一结束就会销毁。所以“从 deleted 恢复数据”这个说法本身并不准确,真正可恢复的是你提前写入归档表的数据,例如 Products_Archive。

恢复操作本身也不建议再绕回触发器,而应直接对原表执行更新:

UPDATE Products 
SET IsDeleted = 0 
WHERE Id IN (SELECT Id FROM Products_Archive WHERE DeletedAt > '2026-08-01');

恢复和归档阶段的性能建议

  • 多条恢复不要长期依赖 WHERE Id IN (子查询) 配合大归档表,容易带来内存压力;更稳妥的方式是使用临时表加 JOIN。
  • 归档表至少应包含 DeletedAt 和 DeletedBy,否则恢复时很难判断是谁删的、什么时候删的。
  • 归档表索引不要堆得过多。写入频繁的场景下,索引维护成本会直接拖慢软删除链路。

一组更稳的使用原则

  • 把 deleted 当成触发器作用域内的临时快照,而不是可持久访问的系统表。
  • 在 UPDATE 场景中,用主键或唯一键把 deleted 与 inserted 成对联查。
  • 涉及归档、审计和恢复时,核心资产不是 deleted 本身,而是你写入归档库的数据结构。
  • 涉及软删除时,先决定是否要阻止物理删除,再选择 INSTEAD OF DELETE 这样的触发方式。
相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。