必须用AFTER触发器,因BEFORE无法获取自增主键、默认值等最终写入值,且事务回滚会导致日志与数据不一致;AFTER才能确保拿到完整已提交的数据快照。

直接用 AFTER 触发器记录变更历史最稳妥,别碰 BEFORE 或 INSTEAD OF —— 前者拿不到自增主键和默认值,后者容易漏数据。
为什么必须用 AFTER 触发器?
日志要反映“最终写入结果”,而 BEFORE 里 NEW.id 是空的(自增还没生成)、NEW.created_at 可能还是 NULL(默认值没生效),INSTEAD OF 则要求你手动重执行原语句,稍错一点就丢行。只有 AFTER INSERT、AFTER UPDATE、AFTER DELETE 能保证读到已提交的完整快照。
-
INSERT日志必须包含NEW.id、NEW.created_at等数据库生成字段 -
UPDATE日志需对比OLD.field和NEW.field,只记真实变化项,避免日志膨胀 -
DELETE只能依赖OLD行,NEW在删除时不可用
MySQL 中如何安全拼接 OLD/NEW 字段?
直接 CONCAT(OLD.name, NEW.name) 很危险:任一字段为 NULL,整条日志内容就变 NULL,导致插入失败或字段丢失。必须显式处理空值。
- 用
CONCAT_WS('|', IFNULL(OLD.name, 'NULL'), IFNULL(NEW.name, 'NULL'))替代CONCAT - 时间字段别裸写
OLD.updated_at,改用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 - 动态拼接内容时用
quote_literal()转义字符串,防 SQL 注入 - 日志表建议关掉
autovacuum_enabled = off,另起定时任务批量清理,避免和业务事务争锁
SQL Server 用 CDC 还是触发器?
如果只是查“哪些行变了”,优先开 CHANGE TRACKING;如果要存“谁在什么时候把 status 从 A 改成 B”,就得用触发器——CDC 不记录旧值/新值,也不存操作人。
-
CHANGE TRACKING开销低、自动清理,但只返回变更行 ID 和版本号 - 触发器能存
USER_NAME()、GETDATE()、字段级差异,但得自己建日志表、写逻辑、压测性能 - 注意
INSERTED和DELETED是临时表,批量操作时含多行,不能假设单行,要用CURSOR或集合操作遍历
真正难的不是写触发器,而是让日志表扛住并发 INSERT、不因字段加减就失效、异常时不卡主流程——上线前必须用真实流量压测日志表延迟,而不是只跑单条 SQL。

















