update()函数仅检测字段是否出现在set子句中,不判断值是否实际变化;需结合old.col != new.col及null安全比较才能准确判定变更。

UPDATE()函数只检查字段是否出现在SET子句里
它不判断值是否真变了,哪怕UPDATE users SET name = OLD.name这种赋原值的操作,UPDATE('name')也返回TRUE。所以不能单靠它做变更判定。
常见错误是写IF UPDATE('status') THEN INSERT INTO log ...——只要SQL里写了status字段,不管有没有改,都会进日志。
- 只能在
BEFORE UPDATE或AFTER UPDATE触发器中用,INSERT/DELETE里调用直接报错:FUNCTION xxx.UPDATE does not exist - 每个字段必须单独调用,
UPDATE('a','b')或UPDATE(['a','b'])语法非法 - 字符串比较要防尾部空格和大小写,必要时加
TRIM()或BINARY
判断字段值是否实际变化必须显式比对
MySQL没有IS DISTINCT FROM,NULL比较容易失效。OLD.col = NEW.col在任一为NULL时结果是UNKNOWN,条件直接跳过。
安全写法是把NULL判断和值比较拆开:
IF (OLD.status IS NULL) != (NEW.status IS NULL) OR OLD.status != NEW.status THEN -- 真正有变更 END IF;
- TEXT/BLOB字段在某些5.7版本中,
CONCAT(OLD.content, 'suffix')可能静默返回NULL,不是OLD失效,而是大字段上下文限制 - 字段名必须和表定义完全一致:大小写、下划线、空格、关键字一个都不能错,含空格字段必须用反引号,如
OLD.`order status` -
OLD和NEW只读,SET OLD.id = 1会报ERROR 1362
AFTER UPDATE才是审计日志的可靠时机
BEFORE UPDATE里虽然能拿到真实旧值,但一旦触发器出错(比如日志表字段超长),整个事务会回滚,业务写入失败。而AFTER UPDATE已提交,记录失败不影响主流程。
-
AFTER INSERT:直接用NEW.*,OLD不存在 -
AFTER UPDATE:必须结合UPDATE('col')+OLD.col != NEW.col双重判断,避免无意义日志爆炸 -
AFTER DELETE:只用OLD.*,注意TEXT字段可能超出日志表VARCHAR长度限制 - 别把整行塞进JSON字段——5.7的JSON函数不支持索引,且
JSON_OBJECT()对NULL和时间类型处理不稳定
复合主键更新时OLD/NEW仍按字段取值
即使主键是(order_id, item_seq),只要UPDATE语句里改了其中某个字段,OLD.order_id和NEW.order_id就如实反映变更前后值。
但要注意:BEFORE UPDATE中可以SET NEW.updated_at = NOW(),却不能SET NEW.order_id = 1002——修改主键字段会破坏约束,MySQL直接拒绝。
- 跨表关联必须用标量子查询:
SET NEW.line_total = (SELECT price * NEW.quantity FROM products WHERE id = NEW.product_id LIMIT 1) - 禁止在触发器里
UPDATE或JOIN触发表本身,否则报Can't update table 'xxx' in stored function/trigger -
OLD和NEW不能跨库引用,OLD.other_db.t.name非法,需显式SELECT ... FROM other_db.t WHERE id = OLD.id
真正容易被忽略的是:你看到日志里某字段值为空,第一反应常是“OLD没拿到”,但大概率是字段定义为TINYINT却存了NULL,或者JSON_OBJECT()遇到二进制字段直接截断报错。











