deleted表仅在DELETE/UPDATE触发器中自动存在,是内存中的临时快照,非真实表,不可创建、删除或跨触发器访问;AFTER INSERT中查询为空;JOIN必须用主键或唯一键,禁止依赖行序;需配合inserted联查、SET NOCOUNT ON、独立归档库及软删设计。

deleted表只在DELETE和UPDATE触发器里自动存在
它不是一张真实表,不能CREATE或DROP,也不能跨触发器访问。只要触发器类型是AFTER DELETE、AFTER UPDATE或INSTEAD OF DELETE/UPDATE,SQL Server 就会在内存中生成这个临时结构,内容是操作前的原始行快照。
常见错误现象:在AFTER INSERT触发器里查deleted表,结果返回空——因为INSERT不涉及旧数据,deleted根本没数据。
- DELETE操作时:
deleted含全部待删行,可直接用于归档或校验 - UPDATE操作时:
deleted只存更新前的值,必须和inserted联查才能比对字段变化 - 别在触发器外用SELECT查
deleted——会报“对象名 'deleted' 无效”
JOIN deleted表必须用主键或唯一列,不能靠顺序
很多人误以为deleted和inserted的行序天然一致,写SELECT TOP 1 FROM deleted加ORDER BY (SELECT NULL)强行取第一行,这是错的。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表做软删,必须拦住物理删除
想靠触发器实现逻辑删除还原,关键不是读deleted,而是不让它真消失。所以必须用INSTEAD OF DELETE,否则触发器还没跑完,行已经从原表物理删除了,deleted虽在但不可靠(比如被其他并发事务干扰)。
- 原表要有
IsDeleted BIT DEFAULT 0字段 - 归档表建议建在**独立数据库**或至少独立文件组,防主库故障导致归档丢失
- 触发器开头必须写
SET NOCOUNT ON,否则某些ORM(如Entity Framework)会把归档INSERT的影响行数当成主语句结果,抛异常 - 别在触发器里调用另一个可能触发自身逻辑的存储过程,容易嵌套死循环
deleted表的数据不可持久,还原依赖你存的归档
deleted表生命周期仅限当前触发器作用域,事务一结束就销毁。所以“从deleted恢复数据”本质上是个伪命题——真正能还原的,是你之前把它抄进归档表(如Products_Archive)的那些行。
还原操作本身不该再走触发器,而是直连原表UPDATE:
UPDATE Products SET IsDeleted = 0 WHERE Id IN (SELECT Id FROM Products_Archive WHERE DeletedAt > '2026-08-01');
- 多条还原别用
WHERE Id IN (子查询)配大归档表,容易超内存;改用临时表+JOIN更稳 - 归档表记得加
DeletedAt和DeletedBy,否则还原时无法区分是谁删的、什么时候删的 - 别给归档表加太多索引——写入频繁时索引维护反而拖慢软删流程

















