能,但需用after触发器手动编写逻辑,通过new/old字段捕获变更、user()和now(3)记录操作人与时间,并存入独立审计表;before不安全,因事务回滚会导致日志与数据不一致。

触发器能自动记录增删改操作吗?能,但得小心用
SQL 触发器确实可以自动捕获 INSERT、UPDATE、DELETE 操作并写入审计表,但它不是“开箱即用”的审计方案。核心限制在于:触发器运行在事务内,若主 SQL 失败,触发器日志也会回滚——这意味着它无法保证日志的持久性(比如系统崩溃时可能丢日志)。真正需要强审计保障的场景,建议搭配数据库自带的 audit log 功能(如 PostgreSQL 的 pg_audit,MySQL 8.0+ 的 audit_log 插件)或应用层异步落库。
PostgreSQL 中用 AFTER INSERT OR UPDATE OR DELETE 写审计日志
PostgreSQL 触发器支持 OLD 和 NEW 记录,适合提取变更前后的字段值。注意必须用 AFTER 触发器(不能用 BEFORE),否则 NEW.id 在自增主键场景下可能为空。
实操要点:
- 审计表需包含
operation('I'/'U'/'D')、table_name、row_id(如主键值)、old_data和new_data(推荐用JSONB存整行快照)、executed_at、user_name(用CURRENT_USER或SESSION_USER) - 触发器函数里用
TO_JSONB(OLD)和TO_JSONB(NEW)获取结构化快照,比拼接字符串更安全、可查 - 避免在触发器里做耗时操作(如远程 HTTP 调用、复杂 JOIN),否则会拖慢主事务
- 对大表启用触发器前,务必在测试环境压测:单条
UPDATE可能因触发器多写一次INSERT,QPS 下降 30%+ 很常见
MySQL 8.0 触发器无法直接获取执行用户?用 USER() 或 CURRENT_USER() 替代
MySQL 触发器不支持 SESSION_USER(),且 USER() 返回的是「客户端连接用户@主机」,而 CURRENT_USER() 返回的是「被授权的账户」,二者在代理用户或权限映射场景下可能不同。审计时建议优先存 USER(),因为它反映真实连接来源。
关键细节:
- MySQL 不支持
OLD.*和NEW.*直接转 JSON,需手动拼字段,例如:CONCAT('{\"id\":', NEW.id, ',\"name\":\"', NEW.name, '\"}')—— 注意引号和转义,否则注入或解析失败 -
UPDATE触发器中,只有被 SET 的字段在NEW中有值,未修改字段仍为OLD值;判断具体哪些字段变了,得显式比较(如IF OLD.email != NEW.email THEN ...) - MySQL 触发器不支持动态表名,审计多张表需为每张表单独建触发器,无法复用同一套逻辑
触发器审计最常踩的三个坑
这些不是理论风险,是线上真实高频故障点:
-
INSERT语句带ON DUPLICATE KEY UPDATE时,MySQL 触发器只触发INSERT或UPDATE其中一个,但业务上它算一次“写入操作”——审计日志会漏掉一半上下文 - 触发器里调用
NOW()或CURRENT_TIMESTAMP,在复制环境中(如 MySQL 主从)可能因 binlog 格式(MIXED或STATEMENT)导致从库时间戳和主库不一致 - PostgreSQL 中,如果审计表本身也挂了触发器(比如加个
BEFORE INSERT做校验),可能引发递归触发,报错stack depth limit exceeded
触发器审计真正难的不是写出来,而是想清楚“这条日志到底要回答什么问题”:是查谁在什么时候改了哪行?还是追溯数据漂移根源?前者用触发器够用,后者往往需要结合 WAL 解析或全量快照比对。











