在触发器中用 = 或 != 比较可能为 NULL 的字段,逻辑必然失效,因返回 UNKNOWN 被当作 FALSE;必须用 IS NULL / IS NOT NULL 判断、IS DISTINCT FROM 检测变更、COALESCE/IFNULL 对函数参数兜底,且空字符串与 NULL 需分开处理。

直接说结论:在触发器里用 = 或 != 比较可能为 NULL 的字段,逻辑一定会“穿透”——不是报错,而是条件永远不成立,后续操作静默跳过。
触发器中 = NULL 判断为什么完全失效
这不是数据库 bug,是 SQL 三值逻辑的必然结果。NEW.status = NULL 返回的是 UNKNOWN,而触发器的 IF 或 WHEN 只响应 TRUE,UNKNOWN 等同于“不满足”,整段分支被跳过。常见现象包括:日志没写、校验没拦、字段没重置,但触发器执行状态却显示“成功”。
- 必须改用
NEW.status IS NULL或NEW.status IS NOT NULL -
!NEW.status、NEW.status = ''这类写法同样无效,它们和= NULL一样无法捕获NULL - 空字符串
''和NULL是两种不同状态,业务上需分开处理时,不能只写一个判断
UPDATE 触发器里检测字段是否真变了
仅靠 OLD.col != NEW.col 在 NULL 参与时会漏判——比如 OLD.name = 'Alice',NEW.name = NULL,这个表达式结果仍是 UNKNOWN,触发器不认为它“变了”。
- MySQL 中必须拆开写:
OLD.name != NEW.name OR (OLD.name IS NULL) != (NEW.name IS NULL),括号不能省,否则解析出错 - PostgreSQL 推荐用原生
OLD.col IS DISTINCT FROM NEW.col,它天然兼容NULL,语义清晰且性能更好 - SQL Server 要显式对比:
DELETED.col IS NULL AND INSERTED.col IS NOT NULL或反向,才能捕获“从有到无”或“从无到有”
对 NULL 字段做函数操作前必须兜底
像 UPPER(NEW.email)、NEW.price * NEW.quantity 这类表达式,只要任意参数是 NULL,结果就是 NULL;更糟的是某些函数(如 UPPER)传入 NULL 会因类型推导失败而直接报错。
- MySQL 用
UPPER(IFNULL(NEW.email, '')),别写成IFNULL(UPPER(NEW.email), '')——拼接或函数调用前就崩了 - PostgreSQL 用
UPPER(COALESCE(NEW.email, '')),注意所有COALESCE参数类型要一致,COALESCE(NEW.email, 123)可能隐式转串并截断 - 数值计算也一样:
COALESCE(NEW.price, 0) * COALESCE(NEW.quantity, 1),别用字符串兜底,比如COALESCE(NEW.price, '0')
字符串拼接、聚合、子查询里的 NULL 渗透风险
触发器里常要拼日志、构造消息或查关联数据,NULL 一旦混入,轻则字段变空,重则整条记录被过滤或计算污染。
- 拼接必须每个字段单独兜底:
CONCAT(IFNULL(NEW.first_name, ''), ' ', IFNULL(NEW.last_name, '')),别只包外层 - 子查询作为标量值时,必须用
COALESCE((SELECT ...), 'default'),否则返回NULL会让主字段显示为空,你以为没查到,其实是查到了NULL - 聚合函数如
SUM()会自动忽略NULL,但COUNT(col)和COUNT(*)行为不同,误用会导致统计偏差
最易被忽略的一点:很多开发者以为“只要用了 IFNULL 就安全了”,但其实它只解决“值替换”,不解决“逻辑分支跳转”。真正要守住边界,第一道关永远是 IS NULL / IS NOT NULL,之后才是兜底和转换。

















