UPDATE()函数仅检查SQL语句SET子句中是否出现指定字段名,与值是否变化无关;仅在BEFORE/AFTER UPDATE触发器中有效,需用OLD.col <=> NEW.col检测实际变更,并注意NULL、精度、JSON等特殊处理。

MySQL触发器里UPDATE()函数到底检查什么
UPDATE()只判断SQL语句的SET子句里是否出现了某个字段名,和值变没变完全无关。比如执行UPDATE users SET email = OLD.email,哪怕email根本没变,UPDATE('email')也返回TRUE。
它只在BEFORE UPDATE或AFTER UPDATE触发器中有效;在INSERT或DELETE触发器里调用会直接报错:FUNCTION db.UPDATE does not exist。
- 别把它当“值变更检测器”,它就是个语法标记器
- 不能传多个字段:
UPDATE('a','b')或UPDATE(['a','b'])都是语法错误 - 字段名必须严格匹配(大小写、反引号):
UPDATE('Email')≠UPDATE('email')(取决于表定义)
真正检测“值是否变了”必须显式比对OLD和NEW
MySQL没有IS DISTINCT FROM,所以得用(空安全等于)来规避NULL陷阱。写OLD.col != NEW.col在任一为NULL时结果是UNKNOWN,IF直接跳过逻辑。
正确写法示例:
IF OLD.status <=> NEW.status THEN -- status 值确实变了(含 NULL → 非NULL / 非NULL → NULL) END IF;
- 字符串字段注意尾部空格和校对规则,必要时加
TRIM()或用BINARY NEW.name <=> BINARY OLD.name - 时间字段若含微秒精度,
DATETIME(6)和DATETIME比较可能因精度截断误判,建议统一转为UNIX_TIMESTAMP()再比 - JSON字段不能直接
<=>,需用JSON_CONTAINS_PATH()或JSON_EXTRACT()提取后比对
组合字段变更判断别嵌套IF,用布尔表达式一次覆盖
想监控status和updated_at两个字段“只要任一变化就触发”,别写两层IF——容易漏掉同时变、或逻辑割裂。
推荐写法:
IF (OLD.status <=> NEW.status) OR (OLD.updated_at <=> NEW.updated_at) THEN INSERT INTO change_log (...) VALUES (...); END IF;
- 后续加字段只需在
OR后追加一项,结构稳定 - 如果业务要求“两个都变才动作”,把
OR换成AND,但务必确认这是真实需求——多数场景OR更合理 - 避免在条件里调用函数(如
NOW()、UUID()),它们在触发器中可能被多次求值,导致不可预期行为
常见踩坑:AFTER触发器里改NEW字段、跨表更新不查存在性
AFTER UPDATE触发器中给NEW.col赋值是无效的,MySQL会静默忽略或报ERROR 1327(取决于SQL模式)。真要改值,必须放在BEFORE UPDATE里。
跨表更新时,比如根据NEW.user_id去user_profile表更新状态,如果user_id不存在,UPDATE影响0行,但不会报错——你的日志或状态就悄悄丢了。
- 所有
UPDATE前加存在性检查:IF EXISTS (SELECT 1 FROM user_profile WHERE id = NEW.user_id) THEN ... - 批量更新时,
ROW_COUNT()返回0不代表失败,而是“没匹配到”,得结合业务判断是否要告警 - 别在触发器里做
SELECT COUNT(*)或关联大表查询,100行更新就会触发100次全表扫描
最易被忽略的是:MySQL 8.0+默认跳过“无实际变更”的UPDATE(如SET name = name),连触发器都不进。查SELECT ROW_COUNT()为0,就得回头检查原始SQL是不是写了恒等赋值。


















