sql server用output子句可同时捕获update前(deleted)后(inserted)值,但需注意类型兼容、禁止函数调用及事务一致性;mysql无output,依赖触发器中old/new伪记录,且须确保引擎为innodb以支持回滚。

SQL Server 用 OUTPUT 子句捕获 UPDATE 前后值
OUTPUT 本身不能直接“对比”新旧值,但能同时访问 DELETED(旧值)和 INSERTED(新值)两个伪表——前提是 UPDATE 语句执行成功且未被回滚。
常见错误是写成 OUTPUT DELETED.Name, INSERTED.Name 就完事,结果发现只记录了单次固定列变更,无法适配多列动态变化场景。
-
DELETED和INSERTED行数严格一一对应,但无排序保证;别依赖“自然顺序”做逻辑判断 - 不能在
OUTPUT中调用GETDATE()、@variable或函数;时间戳、操作人等上下文必须提前声明并传入 - 目标审计表字段类型要兼容:比如
DELETED.Amount是DECIMAL(18,2),审计表的OldValue列就得能接收该精度,否则隐式转换可能截断或报错 - 若需记录所有变更列(而非预设几列),得先
OUTPUT INTO @AuditTable写入表变量,再用UNPIVOT或字符串拼接展开
MySQL 没有 OUTPUT,靠触发器 + OLD/NEW 记录变更
MySQL 不支持 OUTPUT,AFTER UPDATE 触发器里的 OLD.col 和 NEW.col 是唯一可靠来源。注意:触发器里不能修改当前正在更新的表,也不能用事务控制日志写入失败回滚。
典型坑是字段类型不一致导致 CONCAT 失败——比如 OLD.id 是 INT,直接 CONCAT('id:', OLD.id) 在某些版本会静默转空字符串。
- 务必对每个字段显式
CAST(OLD.col AS CHAR)或用IFNULL(CAST(...), '')防空值中断 - 避免在触发器里写大对象(如整行
JSON)到审计表,性能差且难查询;建议只存关键变更字段 - 触发器不拦截通过
LOAD DATA INFILE或绕过 DML 的批量更新,这类操作需额外监控 - 如果表有多个触发器,执行顺序不可控;同一事件(如
AFTER UPDATE)只建一个触发器,内部处理全部逻辑
通用审计表设计要点:别让日志拖垮主业务
审计表不是越全越好。字段过多、索引滥用、频繁 INSERT 会显著拖慢主表 UPDATE 性能,尤其在高并发场景下。
真实踩过的坑:有人把 OldValue 和 NewValue 设为 NVARCHAR(MAX) 并加全文索引,结果每次 UPDATE 都触发日志表锁等待,TPS 直降 40%。
- 主键用
BIGINT IDENTITY而非GUID,减少索引碎片 - 只对高频查询字段建索引,如
(TableName, UpdatedAt),别给OldValue加索引 - 定期归档旧数据(如按月分区),避免审计表膨胀影响
SELECT效率 - 生产环境禁用
FOR JSON PATH全字段快照——它生成大文本、CPU 开销高,且 JSON 解析慢
别忽略事务边界和失败回滚场景
审计写入必须和主业务在同一个事务里,否则会出现“数据改了但日志没写”或“日志写了但数据没改”的不一致。
最容易被跳过的点:存储过程里 UPDATE 后跟了 IF @@ERROR 0 ROLLBACK,但审计插入语句没包进事务块,导致错误时日志残留。
- SQL Server 中,
OUTPUT INTO自动参与当前事务,无需额外 BEGIN TRAN —— 但触发器里的INSERT必须显式包含在事务中 - MySQL 触发器天然属于当前事务,但若审计表引擎不是
InnoDB(如MyISAM),则无法回滚,必须确认引擎类型 - 跨库审计(如日志写到另一台服务器)本质上脱离事务,只能靠最终一致性方案(如 CDC 或消息队列),不能强一致











