状态变更必须用 BEFORE 触发器 + SIGNAL 控制,因MySQL触发器不支持ROLLBACK或事务控制;仅能通过BEFORE UPDATE校验状态合法性并抛出SQLSTATE '45000'异常,且须显式枚举所有允许的状态对、使用ENUM或外键约束status字段、避免触发器中查询其他表或调用存储过程。

状态变更必须用 BEFORE 触发器 + SIGNAL 控制
MySQL 里没法在触发器里写 ROLLBACK 或 START TRANSACTION,所以“主操作失败后回滚状态”这种想法行不通。真正能拦住非法状态转移的,只有 BEFORE UPDATE 触发器配合 SIGNAL SQLSTATE '45000' 抛异常。
- 触发器里别写业务计算,只做状态合法性校验:比如
CASE WHEN OLD.status = 'pending' AND NEW.status = 'approved' THEN 0 ELSE SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Invalid status transition' END - 所有允许的状态对必须显式枚举,不能靠
IF NEW.status != OLD.status这种模糊判断——漏掉一对就会跳过校验 -
SIGNAL的错误码要带上下文,比如SET MESSAGE_TEXT = CONCAT('Status transition denied: ', OLD.status, ' → ', NEW.status),方便排查哪条路径没覆盖
状态字段必须约束为有限枚举值
别让 status 是 VARCHAR(50) 自由输入字段。一旦出现 'pending'、'PENDING'、'pendng' 这类变体,触发器里的 CASE 就会失效。
- 建表时用
ENUM('draft','pending','approved','rejected'),MySQL 会强制校验写入值 - 或者用外键关联
status_types表,确保所有合法值集中管理、可审计 - 应用层传入的状态码必须和数据库定义完全一致,大小写、拼写、空格都不能差
避免在触发器里查其他表或调存储过程
触发器执行是在原 SQL 的同一事务内,任何额外查询都会拖慢主操作。一次 UPDATE 可能从 2ms 涨到 150ms。
- 禁止在触发器里写
SELECT ... FROM users WHERE id = NEW.creator_id这类关联查——除非creator_id字段有索引,且只查必要列 - 更糟的是调用含
INSERT/UPDATE的存储过程,可能引发递归触发或死锁 - 前置权限判断(如“只有审核人能设为 approved”)应拆到应用层,或用
CHECK约束+字段冗余(比如加approver_id字段)
游标、循环、复杂逻辑别塞进存储过程里
状态机本身是数据驱动的转移规则,不是流程引擎。把状态校验和业务动作混在一起,会让存储过程变成不可测、不可调试的黑盒。
- 存储过程只负责接收参数、调用封装好的函数(比如
is_valid_transition(OLD.status, NEW.status))、返回结果 - 状态相关的计算逻辑(如超时自动转态、依赖外部系统回调)交给应用层处理,数据库只管“当前是否允许这个变更”
- 如果真要批量更新状态,优先用集合操作:
UPDATE orders SET status = 'cancelled' WHERE created_at ,而不是游标逐行判
状态机真正的难点不在语法,而在于“谁来决定转移条件”。数据库只适合做静态、确定、无副作用的判断;一旦涉及时间、角色、外部响应、重试策略,就该立刻划清边界,别硬塞进触发器或存储过程里。


















