insert后inserted表存新行、deleted表为空;delete后deleted表存旧行、inserted表为空;update后inserted表存新值、deleted表存旧值,两表行数相等且可依主键join对比变更。

INSERT/UPDATE/DELETE操作后,inserted和deleted表里分别有什么
触发器执行时,inserted 和 deleted 是两个只读内存临时表,结构与触发器所在基表完全一致,但它们的内容取决于当前 DML 类型:
-
INSERT:只有inserted有数据,deleted为空 -
DELETE:只有deleted有数据,inserted为空 -
UPDATE:inserted存新值,deleted存旧值(同一行在两张表中都存在,靠主键或唯一列 JOIN)
注意:批量操作(如 INSERT INTO t SELECT ...)会一次性把所有受影响行写入对应逻辑表,不是逐行触发 —— 所以触发器必须按多行设计,不能假设只有一条记录。
AFTER 触发器中访问 text/ntext/image 列会报错
SQL Server 2000 及后续版本(包括 2022)对 AFTER 触发器中访问大对象类型列有限制:inserted 和 deleted 表里的 text、ntext、image 列不可直接 SELECT 或 JOIN —— 会抛出错误 Cannot access the inserted or deleted table in this context。
解决办法只有两个:
- 改用
INSTEAD OF触发器(它允许访问这些列) - 把列类型升级为
varchar(max)、nvarchar(max)、varbinary(max)(推荐,兼容性更好且无此限制)
别试图用 CAST 或 CONVERT 绕过 —— 错误发生在解析阶段,不是运行时。
JOIN inserted 和 deleted 时必须处理 NULL 值
更新操作中,inserted 和 deleted 表的行数相同,但字段值可能不同;如果用主键 JOIN,看似安全,但实际容易踩坑:
- 若基表主键含
NULL(不推荐但合法),ON i.id = d.id会漏掉匹配行(因为NULL = NULL为 false) - 若触发器逻辑涉及
WHERE i.status d.status,而某列允许 NULL,则NULL 'active'结果为 UNKNOWN,整行被过滤 - 建议统一用
ISNULL(i.col, '') = ISNULL(d.col, '')或(i.col = d.col) OR (i.col IS NULL AND d.col IS NULL)
尤其在审计日志场景下,漏比错更难排查。
触发器里用 @@ROWCOUNT 判断是否真有数据变更
inserted 和 deleted 表可能为空 —— 比如触发器定义为 AFTER UPDATE,但实际语句是 UPDATE t SET x=x WHERE 1=0,这时两张表都空。盲目 SELECT * FROM inserted 不报错,但后续逻辑可能失效。
安全做法是:
- 开头加
IF @@ROWCOUNT = 0 RETURN(注意:必须紧跟在触发器主体第一行,不能被其他语句干扰) - 或显式检查:
IF NOT EXISTS (SELECT 1 FROM inserted) RETURN - 避免在触发器里调用函数或复杂查询后再查
@@ROWCOUNT—— 它会被覆盖
这个检查常被跳过,结果是触发器“静默失败”,看起来没效果,其实逻辑根本没跑。
真正麻烦的不是语法写不对,而是你以为两张表一定有数据、字段一定可比较、类型一定支持访问 —— 这些隐含假设,在上线后批量更新或边缘数据进来时才暴露。










