触发器中获取操作流水ID需外部注入,如通过会话变量传递UUID或序列值,再在触发器内读取;须统一用单个函数处理INSERT/UPDATE/DELETE,记录audit_id、op_type、table_name、row_pk等字段,并建复合索引提升追溯效率。

触发器里怎么拿到操作流水ID
触发器本身不生成ID,得靠外部注入或内部生成。常见做法是:在业务层执行SQL前,先用 SELECT UUID() 或 NEXTVAL('seq_audit_id') 拿到一个唯一ID,再通过 SET LOCAL myapp.audit_id = 'xxx'(PostgreSQL)或 @audit_id := 'xxx'(MySQL)传入会话变量;触发器里直接读这个变量。别指望 UUID() 在触发器里调用就万事大吉——并发下可能重复,且无法和上游请求对齐。
容易踩的坑:
- MySQL 5.7+ 才支持会话级用户变量在触发器中稳定读取,旧版本会丢失值
- PostgreSQL 的
current_setting('myapp.audit_id', true)必须配合SET LOCAL,不能用SET全局设 - 没做
COALESCE(current_setting('myapp.audit_id', true), 'unknown')容错,一旦漏传,整条审计记录ID变成NULL,后续查不出链路
INSERT/UPDATE/DELETE 触发器怎么统一写
三类操作要共用同一个流水ID字段,但数据来源不同:INSERT 是新行,UPDATE 和 DELETE 需捕获旧值。关键不是写三个触发器,而是用一个触发器函数封装逻辑,再让三个触发器都调它。
实操建议:
- 函数参数接收
NEW和OLD,用IF TG_OP = 'INSERT' THEN ... ELSIF TG_OP = 'UPDATE' THEN ...分支处理 - 审计表字段别只存
audit_id,至少加op_type('I'/'U'/'D')、table_name、row_pk(主键值),否则光有ID查不到上下文 - 别在触发器里做JSON序列化——MySQL 5.7+ 的
JSON_OBJECT()性能差,PostgreSQL 的row_to_json(OLD)会把所有字段都包进去,包含大文本或二进制字段时IO暴涨
审计表设计影响追溯效率的关键点
流水ID只是标记入口,真正支撑“追溯”的是审计表结构是否支持反向查询。比如想查某次订单修改影响了哪些库存记录,就得靠 audit_id 联合 table_name 和 row_pk 快速定位。
必须注意:
-
audit_id字段类型用CHAR(36)(UUID)或TEXT,别用VARCHAR(32)截断MySQL的UUID短格式 - 复合索引缺一不可:
CREATE INDEX idx_audit_lookup ON audit_log (audit_id, table_name, row_pk),否则按ID查出来还要全表扫 - 不加
created_at时间戳字段,就无法判断操作先后顺序,尤其跨库同步时,流水ID本身不带时序信息
触发器导致主业务变慢的真实原因
不是触发器逻辑复杂,而是隐式锁和事务膨胀。每次INSERT/UPDATE/DELETE都会让触发器里的INSERT审计记录和主表操作绑在同一个事务里,审计表写入失败 = 主表操作回滚,且审计表写入会延长行锁持有时间。
缓解办法只有两个方向:
- 审计表引擎必须用
LOGGING关闭(Oracle)、innodb_flush_log_at_trx_commit=2(MySQL)或synchronous_commit=off(PostgreSQL),接受极小概率丢审计日志,换主业务不卡 - 真要强一致,就把审计逻辑从触发器抽成异步消息(如写Kafka),但这就不再是“触发器实现”了——题干限定下,只能妥协
- 别在触发器里调远程HTTP或查其他数据库,网络超时直接拖垮整个事务
最常被忽略的是:开发阶段用单条SQL测试看不出问题,一到批量导入或高并发更新,审计表I/O瞬间打满,而错误日志里只报“Lock wait timeout”,没人想到是审计触发器在拖后腿。

















