正确读取Inserted/Deleted表需显式指定字段名,INSERT时仅Inserted有数据,DELETE时仅Deleted有数据,UPDATE时两者均有且须用主键JOIN比对;日志表应统一处理三类操作的字段赋值逻辑,并通过CONTEXT_INFO传递真实操作人,避免使用SYSTEM_USER等不可靠函数。

触发器里怎么正确读取 Inserted 和 Deleted 表
SQL Server 触发器中,Inserted 和 Deleted 是两个只读的逻辑表,不是真实物理表,也不能用 SELECT * 直接查——必须明确字段名。它们结构与触发器所在表一致,但内容取决于操作类型:INSERT 时只有 Inserted 有数据;DELETE 时只有 Deleted 有数据;UPDATE 时两者都有,Inserted 存新值,Deleted 存旧值。
常见错误是写成 SELECT * FROM Inserted 后直接插入日志表,结果在 UPDATE 场景下漏掉主键或时间戳字段映射,导致日志无法关联原记录。
- 始终显式列出字段:比如
SELECT ID, Name, ModifiedDate FROM Inserted,别偷懒用* -
UPDATE触发器中,要用INNER JOIN或EXISTS关联Inserted和Deleted,靠主键(如ID)对齐新旧行 - 如果原表有计算列、默认约束或
GETDATE(),这些不会出现在Inserted/Deleted中——它们只反映 DML 实际修改的列值
INSERT/UPDATE/DELETE 三种场景下日志字段怎么填才不混乱
一个健壮的日志表至少应包含:操作类型(Operation)、原表名(TableName)、主键值(RecordID)、变更字段名(FieldName)、旧值(OldValue)、新值(NewValue)、操作时间(LogTime)、操作人(Operator)。关键难点在于:不同操作类型下,哪些字段有值、哪些为空,必须统一处理逻辑。
-
INSERT:所有变更字段的新值来自Inserted,OldValue全为NULL;RecordID就是Inserted.ID -
DELETE:所有变更字段的旧值来自Deleted,NewValue全为NULL;RecordID就是Deleted.ID -
UPDATE:必须逐字段比对Inserted和Deleted,仅当ISNULL(Inserted.Col1,'') != ISNULL(Deleted.Col1,'')才记录该字段——注意NULL比较要用ISNULL或COALESCE,否则整行被跳过
为什么触发器里不能直接调用 SYSTEM_USER 或 ORIGINAL_LOGIN() 记录操作人
在触发器中用 SYSTEM_USER 得到的是执行 SQL 的登录名(比如 sa),不是应用层用户;而 ORIGINAL_LOGIN() 虽能拿到最初连接的登录名,但 Web 应用常用连接池,实际看到的常是中间件账号(如 IIS APPPOOL\DefaultAppPool)。真要记业务操作人,得靠应用层把用户名作为参数传入(例如用 CONTEXT_INFO 或临时表),否则日志里的“谁改的”永远不准。
- 设置上下文信息示例:
DECLARE @binvar VARBINARY(128) = CAST('admin_user' AS VARBINARY(128)); SET CONTEXT_INFO @binvar; - 在触发器中读取:
CAST(CONTEXT_INFO() AS VARCHAR(128)),记得NULL判断,避免转换失败 - 不要依赖
HOST_NAME()或APP_NAME(),它们容易被伪造,且多线程下可能错乱
性能和事务风险:日志写入慢会拖垮主表操作
触发器在主表事务内同步执行,如果日志表写入涉及远程链接、大字段 TEXT 拷贝、或没建索引的模糊查询,会导致主表 UPDATE 卡住几十秒甚至超时。更隐蔽的问题是:日志表自身也触发了另一个触发器(比如审计表也要记日志),形成递归链。
- 日志表必须有合适索引:至少在
LogTime和TableName上建非聚集索引,避免全表扫描 - 禁用日志表上的触发器(
DISABLE TRIGGER ALL ON audit_log),防止嵌套 - 大字段(如
NVARCHAR(MAX))不要无条件写入日志,加LEN()判断截断,比如LEFT(Inserted.Content, 500) - 测试时用
SET NOCOUNT ON开头,避免触发器返回多结果集干扰客户端
最易被忽略的是:触发器里没加 IF EXISTS (SELECT 1 FROM Inserted) 就直接查 Inserted,结果在 UPDATE 但没真正改数据(比如 SET Status=Status)时,Inserted 和 Deleted 内容完全一样,却仍往日志表插了一堆空记录。

















