NULL不是值而是未知状态,= NULL或!= NULL永远返回UNKNOWN,触发器IF只响应TRUE,故必须用IS NULL/IS NOT NULL判断;误用IFNULL会混淆0与NULL,调试需用日志表实锤。

直接用 = NULL 或 != NULL 判断,永远不生效——这不是 bug,是 SQL 标准行为,所有主流数据库都一样。
为什么 NEW.field = NULL 在触发器里完全不走分支
因为 NULL 不是值,而是“未知”状态。任何与 NULL 的等值比较(包括 NEW.age = NULL、NEW.status != NULL、NOT (NEW.code = NULL))结果都是 UNKNOWN,而触发器的 IF 只响应 TRUE,UNKNOWN 等同于“条件不成立”,直接跳过整个块。
常见现象:字段被更新为 NULL 后,预期的日志插入、默认值填充或校验拦截全没执行,但触发器也不报错,安静失效。
-
WHERE col = NULL查不到任何行,和触发器里逻辑一致 - 加括号、改写成
NOT col = NULL也没用,仍是UNKNOWN - 唯一跨数据库、语义明确、能真正捕获
NULL的写法是IS NULL或IS NOT NULL
正确写法:用 IS NULL 替代所有 = NULL
把原来错误的判断全部替掉,不需要改逻辑结构,只换运算符:
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 是原子判断,不触发隐式转换,不依赖类型推导,索引也能用(尤其在 WHERE 子句中)。
- 别漏掉
ELSEIF分支——很多问题其实出在NULL被跳过后,空字符串或空白字符没被后续逻辑覆盖 -
IF NEW.name IS NULL THEN ... ELSE ... END IF是最安全的二分结构 - 字段名含关键字或空格时,记得加反引号:
NEW.`order status` IS NULL
什么时候该用 IFNULL(),什么时候不该用
IFNULL() 不是 NULL 判断替代品,它是兜底工具:把 NULL 转成确定值,避免后续计算崩掉。
误用示例:IF IFNULL(NEW.price, 0) = 0 THEN —— 这会把“用户填了 0”和“用户根本没填(NULL)”混为一谈。
- 业务需区分两者时,必须先用
IS NULL拦第一道,再对非NULL值做处理 - 字符串操作前先
IFNULL(TRIM(NEW.name), ''),防止TRIM(NULL)返回NULL干扰后续逻辑 -
BEFORE INSERT中不能写SET NEW.name = IFNULL(NEW.name, OLD.name),因为OLD不存在,会报错
调试时怎么确认 NEW/OLD 真实值是不是 NULL
别靠猜,用日志表实锤。建一张轻量级调试表:
CREATE TABLE debug_log ( id BIGINT AUTO_INCREMENT PRIMARY KEY, ts DATETIME, table_name VARCHAR(64), event_type VARCHAR(10), new_data JSON, old_data JSON ) ENGINE=InnoDB;
在触发器开头插入一行:INSERT INTO debug_log (ts, table_name, event_type, new_data, old_data) VALUES (NOW(), 'users', 'UPDATE', JSON_OBJECT('id', NEW.id, 'age', NEW.age), JSON_OBJECT('id', OLD.id, 'age', OLD.age));
- 务必放
BEFORE触发器最前面,否则后续崩溃就看不到原始值 - 不要对
TEXT/BLOB字段直接拼接,用IFNULL(NEW.content, '')包裹再进JSON_OBJECT - 查完立刻删掉日志表或禁用该语句,避免长期拖慢主流程
复杂点在于:IS NULL 看似简单,但一旦和空字符串、空白字符、隐式转换混在一起,边界就模糊了;最容易被忽略的是——你写的 IF 分支里,是否真的覆盖了 NULL、空串、空白串这三种情况。


















