必须显式比对新旧值才能判断字段是否真变:MySQL用 ,PostgreSQL用 IS DISTINCT FROM,SQL Server需从deleted/inserted查值并配合UPDATE()过滤,所有数据库均禁用触发器内查表或耗时操作。

不能只靠 UPDATE() 或 IF UPDATE(col) 判断字段“值”是否真变了——它只告诉你字段有没有出现在 SET 子句里,哪怕 SET email = email 这种无效赋值也算“被更新”。
MySQL:必须用 显式比对 OLD.col 和 NEW.col
MySQL 不支持 UPDATE(col) 语法,直接写会报 Unknown function 'UPDATE'。唯一可靠方式是逐字段比对新旧值,且必须用安全等于操作符 ,因为 != 在任一操作数为 NULL 时返回 UNKNOWN,导致 IF 条件不成立、变更被跳过。
-
OLD.email NEW.email返回0(即 FALSE)才说明值确实变了,适合放进IF NOT (OLD.email NEW.email) - 多个字段合并判断时,写成
IF NOT (OLD.status NEW.status AND OLD.updated_at NEW.updated_at),避免嵌套IF - 别在触发器里查表——比如
SELECT COUNT(*) FROM logs WHERE user_id = NEW.user_id,批量更新 100 行就会执行 100 次查询
PostgreSQL:坚持用 IS DISTINCT FROM
!= 和 = 在 PostgreSQL 中遇到 NULL 同样失效:NULL != NULL 结果是 NULL,不是布尔值,IF 块直接跳过。而 IS DISTINCT FROM 是专为这类场景设计的:它把 NULL 当作一个普通值处理,能正确捕获 NULL → '123'、'123' → NULL、'a' → 'b' 所有真实变更。
- 写法统一:
OLD.phone IS DISTINCT FROM NEW.phone - 对
JSONB、ARRAY等复杂类型也直接可用,无需转字符串或序列化 - 发通知时别塞全量数据:用
NOTIFY order_status_change, NEW.id::text即可,第二个参数上限 8000 字节,监听端应自行查表还原上下文
SQL Server:先用 UPDATE(col) 快速过滤,再显式比对值
UPDATE(col) 是 SQL Server 专属函数,但它只检查字段是否出现在 SET 子句中,无法替代值比对。更糟的是,OLD/NEW 并不存在——你得从逻辑表 deleted 和 inserted 中取值,且必须用 SELECT TOP 1 提取(因为它们是结果集,不是标量)。
- 典型写法:
IF UPDATE(email) AND (SELECT TOP 1 email FROM deleted) IS DISTINCT FROM (SELECT TOP 1 email FROM inserted) - 也可用
COLUMNS_UPDATED()位运算快速判断哪些列被 SET 过,但依然要叠加值比对才能确认“是否真变” - 触发器内禁止调用耗时操作:远程 HTTP、大表 JOIN、存储过程含查询等,否则
UPDATE会被卡住
所有数据库共通的性能雷区
触发器是按行触发的,100 行更新 = 100 次触发器执行。任何 IO 或计算密集型操作都会被放大 100 倍。
- 只做轻量动作:自动更新
updated_at、递增version、校验非空、抛出SIGNAL错误 - 需要关联查询、发邮件、写日志表、调外部 API 的逻辑,一律挪到应用层或异步任务
- 跨库同步场景下,触发器极易漏报变更(如事务未提交、延迟复制),建议源端落变更记录表 + 中间件校验
最易被忽略的一点:字段变更判断本身开销极小,但一旦混进查表、函数调用或动态 SQL,性能会断崖下跌——而且这种问题在单行测试时完全暴露不出来,只有批量更新或高并发时才爆发。

















