非空约束仅拦截null,业务校验需用before触发器;判断空值须用is null或='';修改new字段仅before生效;报错必须用signal;跨表查询须走索引。

非空约束(NOT NULL)只能拦住 NULL,拦不住空字符串 ''、非法格式、跨表状态冲突等业务级问题。真要“预检查”,必须用 BEFORE INSERT 或 BEFORE UPDATE 触发器,在数据落盘前干预。
MySQL 中判断 NULL 和空字符串必须分开写
很多人以为 IF NEW.phone = NULL 或 IF !NEW.phone 能检测空值,其实这两种写法永远不成立——= NULL 返回 UNKNOWN,逻辑判断直接跳过;!字段 对 ''、0、'0' 都可能误判。
- 正确写法只有:
IF NEW.phone IS NULL OR NEW.phone = '' -
TEXT、JSON、BLOB字段也适用IS NULL,但空值和空内容仍是两回事 - 如果业务要求“邮箱必须有 @ 且不能为 NULL 或空串”,得组合:
IF NEW.email IS NULL OR NEW.email = '' OR NEW.email NOT LIKE '%@%'
触发器里改 NEW 字段只在 BEFORE 中生效
比如想把用户提交的空手机号自动转成 NULL,或把小写邮箱统一转大写,只能在 BEFORE INSERT / BEFORE UPDATE 里赋值:
SET NEW.phone = NULLIF(TRIM(NEW.phone), ''); SET NEW.email = UPPER(TRIM(NEW.email));
这些修改会影响后续校验逻辑,也会最终写入表中。但在 AFTER 触发器里对 NEW.phone 赋值完全无效,也不报错——这是最容易被忽略的静默失效点。
校验失败必须用 SIGNAL SQLSTATE '45000',别用 SELECT 报错
MySQL 8.0+ 支持标准异常抛出,这是唯一能明确中断事务、让应用收到错误的方式。常见错误包括:
- 写
SELECT * FROM nonexistent_table模拟报错:低版本兼容但不可靠,容易被误认为是环境配置问题 - 漏写
SIGNAL,只做IF ... THEN ... END IF:条件不满足时啥也不发生,脏数据直接入库 - 在触发器里写
ROLLBACK:直接报错ERROR 1305,因为触发器不能控制事务
正确示例:SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '手机号不能为空且需为11位数字';
跨表校验要防锁表,优先查索引字段
触发器里执行 SELECT 查其他表时,若没走索引或查的是大表,高并发下极易拖慢整个插入流程,甚至引发死锁。
- 必须确保
WHERE条件命中主键或唯一索引,例如:SELECT is_active FROM users WHERE id = NEW.user_id - 避免
SELECT * FROM orders WHERE user_id = NEW.user_id AND status = 'pending'这类无索引扫描 - 更重的校验(如余额、权限树)应该由应用层提前查好传入,触发器只做原子级兜底(比如防止同一订单重复支付)
真正难的不是写触发器,而是判断哪部分校验该放进去、哪部分该留在应用层——一旦开始在触发器里 JOIN 多张大表,你就已经踩进性能陷阱了。











