inserted 和 deleted 表是 sql server 在 dml 事务中原子生成的只读内存快照,结构继承基表但无索引约束,不可修改且不支持 text/ntext/image 列访问,生命周期仅限触发器执行期。

因为它们是唯一能让你在数据变更“发生瞬间”拿到新旧数据快照的机制,没有它们,触发器就只能查当前表状态,无法区分“刚插进来的是哪几行”或“刚删掉的是哪些原始值”。
Inserted 和 Deleted 表不是普通临时表,而是语义级快照
它们不是你用 CREATE TABLE #t 建的临时表,而是 SQL Server 在执行 DML 的同一事务上下文中、原子性生成的只读逻辑表。结构完全继承自触发器所在基表(包括所有列、类型、NULL 属性),但不包含索引、约束或默认值——纯粹是数据副本。
常见错误现象:
- 误以为可以对
inserted或deleted执行UPDATE、INSERT、DELETE—— 实际会报错 “Cannot modify the inserted or deleted tables.” - 在
AFTER UPDATE触发器里写SELECT * FROM inserted WHERE id IN (SELECT id FROM deleted)试图“匹配新旧行”,却忽略 UPDATE 可能影响多行且无天然顺序 —— 必须靠主键/唯一键显式关联,不能依赖物理顺序
为什么必须在内存中?磁盘表不行
触发器运行在事务内部,inserted 和 deleted 的生命周期严格绑定于该次 DML 的事务上下文。如果它们落盘:
- 并发事务可能看到彼此未提交的中间状态,破坏隔离性
- 回滚时需额外清理磁盘临时表,增加事务开销和失败风险
- 无法支持
INSTEAD OF触发器对视图的操作——视图没有物理存储,根本没法建磁盘临时表
性能影响很直接:单行操作时,这两张表几乎零开销;批量操作(如 UPDATE t SET x=1 WHERE y>1000)时,SQL Server 会把整批变更行一次性载入内存,@@ROWCOUNT 可用于判断是否需要走批量处理分支。
容易被忽略的关键限制:text/ntext/image 列不可访问
在 AFTER 触发器中,你不能从 inserted 或 deleted 表里 SELECT 或 WHERE 涉及 text、ntext、image 类型列(哪怕只是 SELECT LEN(col) 都会失败)。这是 SQL Server 2000 起就存在的硬限制,至今未移除。
解决路径只有两个:
- 改用
INSTEAD OF触发器(它允许访问这些大对象列) - 升级字段类型为
varchar(max)、nvarchar(max)、varbinary(max)—— 这些在AFTER触发器中可正常读取
别指望 CAST 或函数绕过——SQL Server 在解析阶段就拒绝含禁用列的查询计划。
最常踩的坑不是语法错误,而是把 inserted 当成“插入历史记录表”,忘了它只活到触发器结束;或是写 UPDATE 触发器时,没意识到 deleted 和 inserted 行数相同但无隐式 JOIN 关系,结果逻辑只对单行有效,批量更新就漏数据。











