无效更新日志源于字段值未变却触发update,因update()函数仅判断set中是否出现字段名,而非值是否真改变;须用old/new显式比对并处理null,避免冗余记录。

无效更新日志几乎都来自字段值没变却触发了UPDATE——触发器照常执行,但日志里存了一堆OLD=NEW的冗余记录。这不是配置问题,是逻辑漏判。
UPDATE()函数不等于“值变了”
SQL Server 的 UPDATE() 只看 SET 子句有没有出现字段名,不管实际值是否改变。比如 UPDATE users SET name = name, email = 'a@b.com',UPDATE(name) 返回 TRUE,但 name 根本没变。
- 别用
IF UPDATE(col)当作“该字段被修改”的判断依据 - 真正要捕获的是“值差异”,必须显式比对:
IF OLD.col != NEW.col OR (OLD.col IS NULL) != (NEW.col IS NULL) - 字段允许 NULL 时,
=和!=都会返回 NULL,所以必须拆开判空
MySQL / PostgreSQL 要靠 NEW/OLD 显式比对
这两库没 UPDATE() 函数,但很多人直接写 IF NEW.amount != OLD.amount 就完事——这在 amount 允许 NULL 时会失效。
- 安全写法是:
IF (NEW.amount != OLD.amount) OR (NEW.amount IS NULL) != (OLD.amount IS NULL) - 如果敏感字段多(比如
amount、currency、invoice_no),别堆一堆OR,改用 JSON_OBJECT 对比(MySQL 5.7+):JSON_OBJECT('amount', NEW.amount, 'currency', NEW.currency) != JSON_OBJECT('amount', OLD.amount, 'currency', OLD.currency) - PostgreSQL 可用
ROW(NEW.amount, NEW.currency) IS DISTINCT FROM ROW(OLD.amount, OLD.currency),一行搞定 NULL 安全比对
AFTER 触发器里别查表再比对
有人想“保险起见”,在 AFTER UPDATE 触发器里再 SELECT 当前行和历史快照比对——这违反 MySQL 触发器禁止读写自身表的规则,会报 ERROR 1442;在 PostgreSQL 虽能运行,但引入额外 I/O 和事务膨胀。
- 所有比对必须基于
OLD和NEW,这是唯一合法、高效、原子的来源 - BEFORE UPDATE 里做判断更早、更省资源,但不能改
NEW后再比对(除非你真要拦截) - 如果业务允许部分字段“伪更新”(如
updated_at总变),就在比对逻辑里排除它们:AND NOT (NEW.updated_at != OLD.updated_at)
最常被忽略的点:软删除字段(如 is_deleted)从 0 改为 1 是有效变更,但从 1 改回 0 可能是误操作——这类语义级判断没法靠通用比对,得硬编码进触发器逻辑。日志不是越全越好,而是要让每条记录都值得被查。










