触发器中new.field = null永远不生效,因sql中null不能用等号比较,结果恒为unknown,而if只响应true;必须改用is null或is not null才能正确捕获null状态。

触发器里写 NEW.field = NULL 永远不进分支,这不是 bug,是 SQL 标准行为。 因为 NULL 不是值,不能用等号比较,结果恒为 UNKNOWN,而 IF 只响应 TRUE。
为什么 = NULL 在触发器里完全失效
常见错误现象:更新某字段为 NULL 后,预期的日志插入、校验拦截或默认值填充逻辑全没执行,但触发器也没报错——它只是安静地跳过了整个 IF 块。
根本原因不是语法错,而是语义陷阱:NEW.age = NULL、NEW.status != NULL、NOT (NEW.code = NULL) 全部返回 UNKNOWN,触发器条件判断直接忽略。
- MySQL、PostgreSQL、SQL Server 都遵循这一规则,不是某个数据库的“坑”
-
WHERE子句里同理,WHERE col = NULL查不到任何行 - 别指望加括号或改写成
NOT col = NULL能绕过,它等价于NOT UNKNOWN,仍是UNKNOWN
必须用 IS NULL 或 IS NOT NULL 替代
这是唯一跨数据库、语义清晰、能真正捕获 NULL 状态的写法。
示例(MySQL BEFORE UPDATE 触发器):
IF NEW.phone IS NULL THEN
INSERT INTO log (msg) VALUES ('phone cleared');
ELSEIF TRIM(NEW.phone) = '' THEN
INSERT INTO log (msg) VALUES ('phone is empty after trim');
END IF;
-
IS NULL是原子判断,不参与类型转换,不触发隐式行为 - 别漏掉
ELSEIF分支——很多问题其实出在NULL被跳过后,空字符串或空白字符没被后续逻辑覆盖 - 如果字段类型是
TEXT或带索引的长字段,IS NULL仍可走索引(取决于引擎和查询上下文),而函数包裹(如IFNULL())会破坏索引使用
IFNULL() 和 COALESCE() 不是判断替代品,是兜底工具
它们的作用是把 NULL 转成一个确定值,让后续计算/拼接/函数调用不崩,但本身不解决“如何识别 NULL”这个问题。
典型误用:
-
IF IFNULL(NEW.price, 0) = 0 THEN ...—— 这会把真实填了0和根本没填(NULL)混为一谈 -
SET NEW.name = IFNULL(NEW.name, OLD.name);在BEFORE INSERT中报错,因为OLD不存在 -
COALESCE(NEW.email, 'N/A')用于UPPER()前是对的,但写成UPPER(NEW.email)再套COALESCE就晚了——UPPER(NULL)已经报错或返回NULL
安全写法示例(PostgreSQL):
NEW.full_name := UPPER(COALESCE(TRIM(NEW.first_name), '') || ' ' || COALESCE(TRIM(NEW.last_name), ''));
复杂场景要分层防护:先判空,再兜底,最后校验
比如处理用户上传的日期字符串,既要接受多种格式,又要防非法值导致触发器中断:
- 必须用
BEFORE INSERT/UPDATE,AFTER无法修改NEW - 每个
STR_TO_DATE()调用后立刻跟IS NOT NULL判断,失败就进下一个分支或设NULL - 避免在
CASE外层包COALESCE,那会掩盖解析失败;要在每个分支内部做 fallback - UPDATE 场景下加
IF NEW.date_str != OLD.date_str,避免无谓重复解析
最易被忽略的一点:NULL 判断失效往往不是孤立问题,而是上游应用没做约束、表结构允许 NULL、又没配 CHECK 导致的连锁反应。修复触发器只是兜底,补上 NOT NULL 或 CHECK 约束才能根治。











