必须用BEFORE INSERT/UPDATE触发器校验,因AFTER执行时数据已落盘,SIGNAL无法回滚;BEFORE可修改NEW或抛错中断,且需用完整SIGNAL语法、避免跨表查COUNT(*)、禁止更新当前表。
能,但必须用对时机、写法和错误机制,否则不是报错就是静默失效。
校验目标是“阻止写入”,不是“事后补救”。AFTER 触发器执行时数据已落盘,SIGNAL 无法回滚;ROLLBACK 在触发器里直接报 ERROR 1305 (42000),MySQL 明确禁止事务控制语句。只有 BEFORE INSERT 和 BEFORE UPDATE 能在语句执行前介入,通过修改 NEW 字段或抛异常可靠中断。
BEFORE INSERT:只读可写 NEW,适合补默认值(如 SET NEW.created_at = NOW())或查其他表校验(如确认用户状态)BEFORE UPDATE:OLD 只读,NEW 可写,必须对比两者才能捕获“从合法改非法”的场景(如 OLD.status = 'shipped' AND NEW.status = 'cancelled' 但未归档发货单)AFTER UPDATE 中尝试 SET NEW.status = 'xxx' 是静默忽略——不报错也不生效,极易踩坑别用 INSERT INTO nonexistent_table 这类野路子,MySQL 5.7+ 必须用 SIGNAL,且语法必须完整:
SQLSTATE '45000'(通用自定义错误码),缺它会报错MESSAGE_TEXT 要用单引号包裹,比如 SET MESSAGE_TEXT = '用户余额不足'
SIGNAL,硬升级比维护一堆不可靠的兜底逻辑更可持续一句 SELECT COUNT(*) > 0 FROM orders WHERE user_id = NEW.user_id 在批量导入时可能卡死——它扫全表,还容易锁表。而 EXISTS 遇到第一条匹配就返回,性能差一个数量级。
EXISTS (SELECT 1 FROM orders WHERE user_id = NEW.user_id AND status = 'pending')
user_id 字段必须有索引,否则 EXISTS 也白搭UPDATE 或 INSERT 当前表,否则触发 ERROR 1442
SELECT,UPDATE other_db.table 直接被拒绝,权限再高也没用最常被忽略的是状态迁移校验和多行插入场景:触发器里写 SELECT @name = name FROM inserted 在多行插入时会报“子查询返回多于一个值”;而只校验 NEW.amount 是否合法,会漏掉 OLD.amount 合法但 NEW.amount 非法的变更。这些细节不提前想清楚,上线后问题很难复现和定位。