在触发器中用= NULL或!= NULL判断必然失效,因返回UNKNOWN被当作FALSE跳过;必须用IS NULL/IS NOT NULL,空字符串与NULL需分开处理,字段变更检测须用数据库特有方式(如MySQL拆解、PostgreSQL用IS DISTINCT FROM)。

触发器里用 = NULL 或 != NULL 一定不生效
这不是语法错误,是 SQL 三值逻辑的必然结果:NEW.status = NULL 返回 UNKNOWN,而触发器的 IF 或 WHEN 只响应 TRUE,UNKNOWN 等同于“条件不满足”,整段逻辑被静默跳过。常见现象包括:校验没拦住、日志没写、字段没重置,但触发器状态却显示“执行成功”。
必须改用 NEW.status IS NULL 或 NEW.status IS NOT NULL —— 这是唯一能稳定进入分支的方式。
-
!NEW.status、NEW.status = ''、IFNULL(NEW.status, '') = ''都不能替代IS NULL判断 - 空字符串
''和NULL是两种独立状态,业务上需区分时,不能只写一个判断 - 对
TEXT、JSON、BLOB字段,IS NULL同样可靠
检测字段是否真正变更时 NULL 会漏判
OLD.col != NEW.col 在任一端为 NULL 时返回 UNKNOWN,触发器会认为“没变”,从而跳过后续处理。比如 OLD.name = 'Alice',NEW.name = NULL,这个表达式不成立,但业务上这明显是“清空了姓名”。
不同数据库的应对方式差异很大:
- 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), '')——后者在UPPER(NULL)阶段就崩了 - PostgreSQL:用
UPPER(COALESCE(NEW.email, '')),注意所有COALESCE参数类型要一致,COALESCE(NEW.email, 123)可能隐式转串并截断 - 数值计算也一样:
COALESCE(NEW.price, 0) * COALESCE(NEW.quantity, 1),别用字符串兜底,比如COALESCE(NEW.price, '0')
阻止 NULL 插入关键字段必须用 SIGNAL 中断
仅靠 SELECT 或写日志无法阻止 NULL 写入。MySQL 中唯一有效方式是 SIGNAL SQLSTATE '45000' 主动报错并中断当前语句。
常见陷阱:
- 写成
IF NEW.phone = NULL THEN ...→ 永远不触发,校验形同虚设 - 用
INSERT INTO error_log替代SIGNAL→ 数据仍会写入,只是多了一条日志 - 只在
INSERT触发器中检查,忽略UPDATE→ 攻击者可先插入再清空字段绕过 - 外键、自增主键、已有
NOT NULL约束的字段,不该再加触发器校验——约束本身更快、更明确、更可靠
真正该上触发器的,是那些“业务级非空”场景:比如 id_card 字段允许为 NULL,但一旦提供就必须是 18 位,这种复合逻辑才值得用触发器。










