MySQL用OLD.col取更新前值,PostgreSQL需在函数中用OLD/NEW变量,SQL Server依赖DELETED/INSERTED表,SQLite仅BEFORE触发器支持OLD。

触发器里怎么拿到更新前的旧值
MySQL 的 BEFORE UPDATE 触发器中,用 OLD.字段名 可以读取更新前的值;NEW.字段名 是更新后的值。PostgreSQL 则必须在 FOR EACH ROW 触发器函数里通过 OLD 和 NEW 记录变量访问——不能直接写 OLD.id,得在函数体里声明变量或直接返回拼接结构。
常见错误是误以为所有数据库都支持 OLD/NEW 点语法,比如在 SQL Server 里它压根不存在,得靠 INSERTED 和 DELETED 临时表;SQLite 虽支持但仅限于 BEFORE 触发器,AFTER 里 OLD 已不可用。
- MySQL:直接用
OLD.col、NEW.col,注意字段名大小写敏感(取决于系统配置) - PostgreSQL:必须写函数,
RETURN NEW或RETURN NULL控制是否执行原 UPDATE - SQL Server:查
DELETED表得用JOIN或子查询,别漏WHERE关联主键,否则可能多行匹配
差异字段怎么存才不丢信息又方便查
别把整行 JSON 或字符串塞进一个 diff_text 字段——看着省事,后面想查“哪些记录改过 status”,就得全表扫描 + JSON 解析,性能崩得快。
推荐用宽表结构:固定字段如 table_name、row_id、updated_at、updated_by,再加一组 col_name(varchar)、old_value(text)、new_value(text)——一行只存一个字段的变更。这样加索引简单,按字段名查、按时间范围查都快。
- 如果变更字段多,单次 UPDATE 可能生成多条日志,用事务包住整个触发器逻辑,避免部分写入
-
old_value/new_value统一转成字符串存储(哪怕原字段是 int 或 bool),避免类型不一致导致比对失败 - 敏感字段如
password_hash要在触发器里显式跳过,别等日志表里漏出密文
触发器性能卡在哪几个地方
最常被忽略的是锁等待和日志写放大。比如 MySQL 的行级锁,在 BEFORE UPDATE 触发器里做 INSERT 写日志时,会延长原 UPDATE 的临界区——并发高时,用户明显感觉“改个昵称要等两秒”。
另一个坑是没控制日志量:用户批量更新 10 万行,触发器就往日志表插 10 万 × 字段数 条记录,磁盘 IO 直接打满,主库同步延迟飙升。
- 上线前用真实数据压测,重点看
SHOW PROCESSLIST里有没有大量Waiting for table metadata lock - 给日志表加复合索引:
(table_name, row_id, updated_at),不然按业务主键查历史变更极慢 - 考虑异步化:触发器只写轻量消息到 Redis 或 Kafka,由后台服务消费写日志表——但得接受最终一致性,不能强求“改完立刻可查”
跨数据库迁移时触发器最痛的三个点
语法只是表象,真正麻烦的是语义差异。比如 PostgreSQL 触发器函数返回 NULL 会取消原操作,而 MySQL 的 BEFORE 触发器没法阻止 UPDATE(除非抛异常),你得靠 IF ... THEN SIGNAL 模拟。
还有更隐蔽的:Oracle 的触发器默认是 FOR EACH STATEMENT,不是 ROW;SQL Server 的 INSTEAD OF 和 AFTER 触发器行为完全不同,拿错类型会导致旧值永远读不到。
- 别直接复制触发器代码,先查目标库文档里 “trigger old new values” 这几个关键词
- 字段类型映射要小心:MySQL 的
TINYINT(1)常被当布尔用,但 PostgreSQL 的BOOLEAN存的是true/false,转字符串后一个是"1",一个是"true" - 测试必须覆盖空值场景:
OLD.col IS NULL和NEW.col = ''在不同库里的比较逻辑可能不一致
真正在意变更追溯的系统,往往不是卡在“怎么写触发器”,而是卡在“怎么让触发器不拖垮线上服务”。字段选哪些、日志存多久、查的时候要不要加缓存——这些决策比语法细节影响更大。

















