应使用 NOT (NEW.column_name <=> OLD.column_name) 判断字段是否真实变更,该操作符可安全处理 NULL;若需精确比较字符串,应配合 BINARY 或 COLLATE utf8mb4_bin。

UPDATE触发器里怎么知道某个字段真的被改了
直接比 NEW.column_name != OLD.column_name 不可靠——NULL 值会让整个表达式返回 NULL,结果被当作 false 处理,导致误判。比如 OLD.name = NULL 且 NEW.name = 'Alice',NEW.name != OLD.name 实际求值为 NULL,不是 true。
安全判断字段是否变更的写法
必须显式处理 NULL:MySQL 没有 IS DISTINCT FROM(PostgreSQL 有),得靠逻辑组合。
NEW.column_name != OLD.column_name OR (NEW.column_name IS NULL) != (OLD.column_name IS NULL)- 更简洁常用的是:
NOT (NEW.column_name OLD.column_name)——是 MySQL 的“安全等于”操作符,对 NULL 也返回布尔值:NULL NULL为 true,'a' NULL为 false - 如果字段有默认值或约束(如 NOT NULL),可省略 NULL 判断,但建议统一用
避免漏 case
在 BEFORE UPDATE 触发器里做条件逻辑
常见需求:仅当 status 字段实际变化时才更新 updated_at 或写日志。
DELIMITER $$
CREATE TRIGGER update_updated_at
BEFORE UPDATE ON orders
FOR EACH ROW
BEGIN
IF NOT (NEW.status <=> OLD.status) THEN
SET NEW.updated_at = NOW();
INSERT INTO order_status_log(order_id, old_status, new_status)
VALUES (OLD.id, OLD.status, NEW.status);
END IF;
END$$
DELIMITER ;
注意:NEW.updated_at = NOW() 写在 IF 内;若放外面,每次 UPDATE 都会覆盖时间戳,失去“仅变更时更新”的语义。
字符串字段要注意大小写和空格
如果字段是 VARCHAR 且用了非二进制排序规则(如 utf8mb4_0900_as_cs 以外的默认 collation),'abc' 'ABC' 可能为 true(取决于 collation)。需要精确比较时:
- 强制用二进制比较:
CAST(NEW.title AS BINARY) != CAST(OLD.title AS BINARY) - 或用
BINARY前缀:NEW.title != OLD.title COLLATE utf8mb4_bin - 空格差异(如
'abc 'vs'abc')也会被普通比较忽略,TRIM()要按业务决定是否介入
真正难缠的是业务语义上的“未修改”:比如前端传空字符串覆盖原 NULL,或 JSON 字段格式化后内容相同但空白不同——这些已超出触发器能自动识别的范围,得靠应用层清洗或额外校验字段。


















