output子句本身不审计,必须配合into存入表中才能持久化;它仅返回结果集,不自动落盘,且不参与事务回滚清理。

OUTPUT 子句本身不审计,它只返回当前语句影响的行 —— 想要“记录修改历史”,必须配合 INTO 把结果存到表里,否则输出一过就丢。
OUTPUT 必须搭配 INTO 才能持久化审计数据
很多人写完 OUTPUT INSERTED.* 就以为日志有了,其实它只是把结果集返回给客户端。SQL Server 不会自动落盘。真正要存档,得用 INTO 显式指定目标表或表变量。
-
INTO后只能跟已存在的表(含临时表、表变量),不能是新CREATE TABLE语句 - 目标表结构必须和
OUTPUT列列表严格匹配(列数、类型、顺序) - 如果想同时存旧值+新值+操作类型,得自己在目标表里加字段,比如
Action VARCHAR(10)、ModifiedAt DATETIME2 - 直接用表变量(
@AuditLog)适合单条语句轻量捕获;用物理表(EmployeesAudit)才适合长期审计
UPDATE 场景下 DELETED 和 INSERTED 都可用,但别混淆逻辑
UPDATE 是唯一能同时访问 DELETED(更新前)和 INSERTED(更新后)的 DML 操作。但要注意:
- 不能在
INSERT里引用DELETED,SQL Server 会报错:The column prefix 'DELETED' does not match with a table name or alias name used in the query. - 同理,
DELETE中不能用INSERTED - 想标记操作类型?得靠外部逻辑:比如在
UPDATE语句里手动加常量'UPDATE'到INTO目标列中 - 示例:
OUTPUT DELETED.ID, DELETED.Name AS OldName, INSERTED.Name AS NewName, 'UPDATE' AS Action INTO EmployeesAudit
INSERT/UPDATE/DELETE 审计表设计的关键兼容点
一张通用审计表要支撑三种操作,字段设计得留余地:
- 主键建议用自增
ID INT IDENTITY,别复用原表主键(比如EmployeeID可能为 NULL) - 原表字段尽量用
NVARCHAR(MAX)或VARBINARY(MAX)存序列化值,避免类型不匹配;或者只存关键差异字段(如 ID + 修改时间 + 操作类型) -
ChangedData字段若用FOR XML PATH(''),注意特殊字符转义问题;FOR JSON PATH('')(SQL Server 2016+)更直观但需确认兼容性 - 不要在审计表上建过多索引——高频写入场景下,索引反而拖慢主表 DML 性能
触发器里用 OUTPUT 是陷阱,别这么干
有人试图在 AFTER INSERT 触发器里再写个 INSERT ... OUTPUT 去记日志,这属于叠床架屋:
- 触发器本身已是额外开销,再嵌套
OUTPUT+INTO会放大延迟 - 触发器里的
INSERTED/DELETED已是虚拟表,直接INSERT INTO AuditTable SELECT ... FROM INSERTED更简洁可靠 -
OUTPUT在触发器中无额外价值,还可能因作用域问题导致目标表不可见(尤其跨数据库时) - 真要用
OUTPUT,就让它在应用层或存储过程中直连主 DML 语句,减少中间跳转
最易被忽略的一点:OUTPUT 的执行时机紧贴 DML,但它不参与事务回滚的“自动清理”——如果你 INTO 到一个普通表,而主事务后来 ROLLBACK 了,审计记录不会自动删。需要你自己在事务外补 cleanup 逻辑,或改用临时表 + 应用层兜底。










