mysql和postgresql均通过old获取修改前行、new获取修改后行;insert仅可用new,delete仅可用old,update两者皆可;须显式列出字段或用json_object/to_jsonb构造,禁用old.*通配符。

触发器里怎么拿到修改前后的值
MySQL 和 PostgreSQL 的触发器语法差异大,但核心逻辑一致:用 OLD 拿修改前的行,NEW 拿修改后(INSERT 只有 NEW,DELETE 只有 OLD,UPDATE 两者都有)。别直接写 OLD.* 或 NEW.* —— 触发器里不支持这种通配符赋值。
实操建议:
- 显式列出字段,比如
INSERT INTO audit_log (table_name, op_type, old_value, new_value, updated_at) VALUES ('users', 'UPDATE', OLD.email, NEW.email, NOW()) - 对 JSON 字段可拼接:PostgreSQL 用
to_jsonb(OLD),MySQL 8.0+ 用JSON_OBJECT('id', OLD.id, 'name', OLD.name) - 避免在触发器里做复杂字符串拼接或调用函数(如
UUID()在某些 MySQL 版本会报错),先在应用层或存储过程里处理好再传入
审计日志表设计要注意哪些字段
光记 OLD/NEW 不够,没上下文就查不出谁改的、从哪改的。审计表至少得包含这几个字段:
-
id(自增或 UUID) -
table_name(varchar,记录是哪张表被改) -
record_id(对应原表主键值,类型要和原表一致,比如BIGINT或VARCHAR(36)) -
op_type('INSERT'/'UPDATE'/'DELETE') -
old_data和new_data(JSON类型最灵活,MySQL 5.7+ / PG 9.4+ 都支持) -
updated_by(不能依赖CURRENT_USER()—— 它返回数据库用户,不是业务用户;得从应用传参或用SET @app_user = 'xxx'配合触发器读取) -
updated_at(用NOW()或CURRENT_TIMESTAMP,别用应用时间,避免时钟不同步)
MySQL 触发器报错 “Can't update table in stored function/trigger” 怎么办
这是 MySQL 的硬限制:触发器里不能对**同一张表**做 DML 操作(比如在 users 的 AFTER UPDATE 里再去 UPDATE users),哪怕只是 SELECT 也不行(某些版本会卡死)。审计日志必须写到**独立的表**,且该表不能是触发器所在表的别名或视图。
常见踩坑点:
- 误把审计表建在同一个库但用了同义词或 FEDERATED 引擎 → 实际还是被识别为“同一表”
- 在触发器里写了
SELECT COUNT(*) FROM users WHERE id = OLD.id→ 直接报错,删掉 - 想记录执行 SQL 语句本身?MySQL 不提供
QUERY_STRING系统变量,别试了;PG 可用current_query(),但仅限log_statement = 'all'且不进触发器
PostgreSQL 中触发器函数必须用 PL/pgSQL 写吗
不是必须,但推荐。PostgreSQL 允许用 SQL、PL/pgSQL、plpythonu 等语言写触发器函数,但只有 PL/pgSQL 支持 IF/THEN/ELSE 判断操作类型,也最容易处理 NULL 字段(比如 OLD.name IS DISTINCT FROM NEW.name 比 OLD.name != NEW.name 更安全)。
写法要点:
- 函数返回类型必须是
TRIGGER - 最后一行必须是
RETURN NEW(BEFORE)或RETURN NULL(AFTER),漏掉会报错 - 不要在函数里
COMMIT或ROLLBACK—— 触发器运行在事务内,由外部事务统一控制 - 如果审计量大,考虑异步落库(比如发消息到 Kafka),否则高并发 UPDATE 可能拖慢主业务
INSERT 权限;触发器本身不加索引,但审计表的 table_name + record_id + updated_at 组合最好建复合索引,否则查某条记录的历史时会全表扫。











