必须同时校验old.status与new.status的组合,显式枚举合法跳转路径并用signal报错拦截非法变更,禁止仅校验目标状态或修改new字段掩盖问题,status字段须定义为not null enum或带check约束确保取值合法。

触发器里怎么判断状态变更是否合法?
状态机流转的核心是「只允许特定状态之间跳转」,比如 created → paid 合法,但 shipped → created 绝对禁止。MySQL 触发器没法直接定义状态图,得靠 IF + OLD.status 和 NEW.status 显式比对。
常见错误是只校验目标状态(如 NEW.status IN ('paid', 'cancelled')),却忽略来源状态。结果导致 delivered 直接改回 paid 也能通过。
- 必须同时检查
OLD.status和NEW.status的组合,例如:IF OLD.status = 'created' AND NEW.status NOT IN ('paid', 'cancelled') THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Invalid transition from created'; END IF; - 把所有合法跳转写成独立
IF分支,比用CASE更易读、更易调试 - 注意
OLD.status在 INSERT 触发器里为 NULL,需单独处理(比如只允许 INSERT 初始状态为created)
BEFORE UPDATE 触发器能拦截非法 UPDATE 吗?
能,但必须用 SIGNAL 主动报错,不能只写日志或设默认值。MySQL 触发器里修改 NEW.* 字段(比如强行重置状态)属于掩耳盗铃——业务层看到的仍是非法值,且掩盖了真实问题。
典型陷阱是误用 SET NEW.status = OLD.status 来“修复”非法变更。这会让 SQL 语句看似成功执行,但实际状态没变,下游业务逻辑可能因状态未更新而卡住。
- 只在明确需要拒绝时调用
SIGNAL SQLSTATE '45000',错误码固定用45000表示用户自定义异常 - 不要在触发器里调用存储过程做状态校验——增加不可控延迟,且无法保证原子性
- UPDATE 触发器中,若
NEW.status = OLD.status,建议直接LEAVE或跳过校验,避免无谓开销
订单表字段设计影响触发器可靠性吗?
影响很大。如果 status 字段是 VARCHAR 类型且没加 CHECK 约束,就可能出现拼写错误状态(如 'paied'),触发器里的字符串比对会失效。
另一个坑是状态字段允许 NULL。NULL 和任何字符串比较都返回 FALSE,OLD.status = 'created' 在旧记录为空时恒为假,导致校验逻辑被绕过。
-
status必须定义为ENUM('created','paid','shipped','delivered','cancelled'),或至少加CHECK (status IN ('created','paid',...)) - 确保该字段
NOT NULL,INSERT 时强制指定初始状态 - 避免用数字编码状态(如 1/2/3),可读性差,且触发器里写
OLD.status = 2完全无法直观理解意图
为什么不能只靠应用层校验状态机?
因为绕过应用直连数据库的场景太多:运维手动修数据、其他服务共用库、SQL 工具误操作、甚至备份恢复后状态不一致。触发器是最后一道防线,它不信任任何调用方。
但别指望它解决所有问题——它不阻止 DELETE 订单,也不校验关联表(如支付表状态是否匹配)。真正健壮的状态机需要数据库约束 + 应用层幂等 + 外部事件审计三者配合。
最常被忽略的一点:触发器本身没有事务上下文外的重试机制。一旦 SIGNAL 报错,整个 UPDATE 就回滚,但业务代码未必能正确捕获这个 MySQL 错误码,反而当成连接超时或网络异常处理。











