update触发器必须用after而非before,以确保能正确获取old.和new.值并保证事务一致性;审计表应存完整快照(推荐json类型),避免拆字段;触发器需过滤空更新,并从会话上下文取用户身份。

UPDATE 触发器必须用 AFTER 而不是 BEFORE
因为你要在原记录被修改后,才能拿到旧值(OLD.*)和新值(NEW.*)做对比并存档。用 BEFORE UPDATE 时,NEW.* 可能还没完全生效,且无法保证事务一致性;而 AFTER UPDATE 确保主表更新已提交(或至少处于同一事务上下文),审计记录与主数据严格同步。
常见错误是写成 BEFORE UPDATE 后试图读 NEW.updated_at 或做条件判断,结果取到默认值或 NULL,导致审计记录漏字段或逻辑错乱。
- PostgreSQL / MySQL 8.0+ / SQL Server 都支持
AFTER UPDATE触发器 - SQLite 不支持
AFTER UPDATE触发器,只能用BEFORE UPDATE+ 手动保存旧值到临时表(不推荐用于生产审计) - 触发器内禁止修改当前正在被 UPDATE 的表,否则会递归触发或报错(如 PostgreSQL 的 “cannot update table in trigger”)
审计表设计要包含 OLD 和 NEW 的完整快照字段
别只存变更字段——业务查询常需要“某条记录在某个时间点的完整状态”,比如排查订单状态跳变时,光记 status 从 1→2 不够,还得知道当时 payment_amount、shipping_address 是多少。
推荐结构:audit_id(自增)、table_name(如 'orders')、row_id(主表主键值)、action('UPDATE')、old_data(JSON 类型存 OLD.*)、new_data(JSON 类型存 NEW.*)、updated_by(从应用传入或从 CURRENT_USER 取)、created_at(用 NOW())。
- MySQL 5.7+ 支持
JSON类型,直接JSON_OBJECT('id', OLD.id, 'name', OLD.name) - PostgreSQL 推荐用
jsonb+to_jsonb(OLD),体积小、可索引 - 避免把每个字段拆成单独列(如
old_status,new_status),扩展性差,加字段就得改触发器和审计表
触发器里别依赖应用层传的 user_id,要用 SESSION 变量或数据库角色
很多开发者在 UPDATE 语句里拼 updated_by = 123,再让触发器读这个字段——这不可靠:应用可能忘记传、SQL 注入绕过、或批量脚本直连数据库时根本没设该字段。
更稳的做法是让触发器从数据库会话上下文中取身份信息:
- PostgreSQL:
CURRENT_USER或current_setting('app.user_id', true)(需应用提前执行SET app.user_id = '123') - MySQL:
USER()或自定义变量@current_user_id(应用执行SET @current_user_id = 123) - SQL Server:
SUSER_SNAME()或CONTEXT_INFO()(需应用调用SET CONTEXT_INFO) - 注意:这些值可被恶意会话伪造,如需强审计,必须配合应用网关层日志+数据库登录审计联合验证
UPDATE 触发器性能敏感,必须加 WHERE 条件过滤无意义更新
用户点“保存”但所有字段都没变,也会触发 UPDATE —— 这类空更新如果全进审计表,半年就生成百万无效记录,查起来卡,备份膨胀。
在触发器里做浅层比对(尤其主业务字段),跳过无变更的行:
IF (OLD.status != NEW.status OR OLD.amount != NEW.amount) THEN INSERT INTO audit_log (...) VALUES (...); END IF;
- 别比对时间戳字段(如
updated_at),它本身就在变,会导致每次必录 - 字符串字段注意 NULL 安全比较:用
COALESCE(OLD.name, '') != COALESCE(NEW.name, ''),否则NULL != 'abc'结果是 UNKNOWN - 大字段(如
TEXT、JSON)比对开销大,可跳过或只比哈希(如MD5(OLD.payload) != MD5(NEW.payload))
真正难的不是写触发器,是想清楚哪些字段变更算“有效变更”、哪些用户行为必须留痕、以及当主表每秒上百次 UPDATE 时,审计表的索引和分区策略怎么跟上——这些得看业务 SLA,不是加个触发器就完事。










