SQL Server 2019 支持用AFTER触发器记录日志,需通过inserted/deleted伪表判断操作类型:INSERT(有inserted无deleted)、UPDATE(两者均有)、DELETE(有deleted无inserted),并用主键JOIN确保新旧值匹配。

直接上结论:SQL Server 2019 完全支持用 AFTER INSERT, UPDATE, DELETE 触发器记录变更日志,但必须注意字段类型对齐、NULL 处理、多行操作和事务上下文——否则日志会漏写、截断或报错。
触发器里怎么区分 INSERT/UPDATE/DELETE 操作
不能只靠 IF UPDATE(col) 判断,它只说明该列参与了 UPDATE 语句(哪怕值没变),真正判断操作类型得查 inserted 和 deleted 两个伪表:
-
EXISTS(SELECT 1 FROM inserted) AND NOT EXISTS(SELECT 1 FROM deleted)→ INSERT -
EXISTS(SELECT 1 FROM inserted) AND EXISTS(SELECT 1 FROM deleted)→ UPDATE -
NOT EXISTS(SELECT 1 FROM inserted) AND EXISTS(SELECT 1 FROM deleted)→ DELETE
注意:UPDATE 场景下,inserted 和 deleted 行数一定相等,但顺序不保证对应,必须用主键 JOIN(如 ON i.id = d.id),不能依赖物理顺序。
日志表字段设计最容易踩的坑
日志表字段长度、精度、NULL 性必须 ≥ 原表对应字段,否则插入失败。比如原表 name NVARCHAR(50),日志表对应字段要是 NVARCHAR(100) 或更大;原表 price MONEY,日志表不能用 DECIMAL(10,2)(可能溢出)。
常见错误现象:String or binary data would be truncated —— 这不是警告,是硬性报错,整条触发器逻辑会回滚,导致原 DML 也失败。
建议做法:
- 用
sys.columns查询原表字段定义,生成日志表建表脚本 - 所有字符串字段统一用
NVARCHAR(MAX)(仅用于日志,性能影响可控) - 数值类字段优先用
SQL_VARIANT或宽泛的DECIMAL(38,10)存原始值
记录“改了哪些字段”需要动态拼 SQL 吗
不需要硬编码每个字段,但也不能全靠 CONCAT 粗暴拼接——那样丢失字段粒度。推荐用游标 + QUOTENAME 动态比对(适用于字段不多、审计要求细的场景):
DECLARE column_cursor CURSOR FOR
SELECT name FROM sys.columns
WHERE object_id = OBJECT_ID('YourTable')
AND name NOT IN ('id', 'created_at', 'updated_at');
然后在循环里构建类似这样的语句:
INSERT INTO audit_log (table_name, column_name, old_value, new_value)
SELECT 'YourTable', 'col1',
CAST(d.col1 AS NVARCHAR(MAX)),
CAST(i.col1 AS NVARCHAR(MAX))
FROM inserted i
FULL OUTER JOIN deleted d ON i.id = d.id
WHERE i.col1 <> d.col1 OR (i.col1 IS NULL AND d.col1 IS NOT NULL) OR (i.col1 IS NOT NULL AND d.col1 IS NULL);
关键点:NULL 必须显式判断,col1 col1 在任一为 NULL 时结果为 UNKNOWN,不会进入 WHERE。
为什么触发器里用 GETDATE() 而不用 SYSDATETIMEOFFSET()
GETDATE() 返回 DATETIME,精度 3.33ms,足够满足多数审计时间戳需求;而 SYSDATETIMEOFFSET() 是带时区的 DATETIMEOFFSET,存储开销大、索引效率低,且 SQL Server 2019 默认事务内所有 GETDATE() 返回相同值(语句级一致性),更利于日志时间对齐。
真正要注意的是:不要在触发器里调用 SYSTEM_USER 或 SUSER_SNAME() 获取操作人——B/S 应用下它们返回的是应用连接池账号(如 sa 或 app_user),不是真实操作人。真实用户名得由应用层传入字段(如 modified_by)再记入日志。

















