记录变更日志必须用AFTER触发器,因BEFORE中无法获取自增主键、默认值等最终写入值,只有AFTER INSERT/UPDATE/DELETE才能确保拿到完整已提交的数据快照。

触发器该用 BEFORE 还是 AFTER?
记录变更日志必须用 AFTER 触发器,不能用 BEFORE。因为 BEFORE 里读不到最终写入的值(比如自增主键、默认值、计算列),日志会漏掉关键字段。只有 AFTER INSERT、AFTER UPDATE、AFTER DELETE 才能确保拿到完整、已提交的数据快照。
-
INSERT日志要记录新行所有字段,包括数据库生成的id和created_at -
UPDATE日志必须对比旧值和新值,只记变化字段(避免日志膨胀) -
DELETE日志只能从OLD行取数据,NEW在删除时不可用
MySQL 中如何安全获取 OLD/NEW 值并避免 NULL 问题?
MySQL 触发器里 OLD 和 NEW 是伪记录,但直接拼接字符串容易因 NULL 导致整个日志字段为 NULL。别用 CONCAT(OLD.name, NEW.name),改用 CONCAT_WS 或显式判断:
CONCAT_WS('|', IFNULL(OLD.name, 'NULL'), IFNULL(NEW.name, 'NULL'))- 对时间字段用
IFNULL(OLD.updated_at, '1970-01-01'),避免日志插入失败 - 更新日志中,用
IF(OLD.status != NEW.status, CONCAT('status:', OLD.status, '→', NEW.status), NULL)只捕获真实变更
PostgreSQL 的触发器函数怎么写才不拖慢主表性能?
PG 必须把日志逻辑封装成独立函数,且函数必须声明为 SECURITY DEFINER 并设为 VOLATILE。否则触发器可能跳过执行,或因权限不足写不进日志表。
- 函数内避免
SELECT查询其他大表,日志写入必须是纯INSERT - 用
EXECUTE动态拼接日志内容时,务必用quote_literal()转义字符串,防止 SQL 注入 - 日志表建议关闭自动 vacuum 分析(
autovacuum_enabled = off),另起定时任务批量清理,避免和业务事务争锁
SQL Server 的 INSTEAD OF 触发器适合做变更日志吗?
不适合。虽然 INSTEAD OF 能拦截操作,但它要求你手动重写原 INSERT/UPDATE/DELETE 语句——稍有遗漏就会丢数据。变更日志场景应坚持用 AFTER 触发器,哪怕它不能修改当前事务数据。
-
AFTER触发器在事务提交后运行,日志写入和主表更新天然一致 - 如果真需要修改原始行为(比如过滤非法字段),应在应用层或存储过程里做,而不是靠触发器补救
- 注意
INSERTED和DELETED临时表在批量操作时可能含多行,日志循环插入必须用CURSOR或集合操作,不能假设单行
触发器本身不难写,难的是让日志表不成为性能瓶颈、不因异常中断主流程、不因字段变更就失效。上线前一定要用真实并发量压测日志表的 INSERT 延迟,而不是只验证单条语句是否成功。

















