before update触发器是校验状态跳转合法性的唯一可靠时机,必须显式枚举允许路径、禁用模糊逻辑、拒绝自动修正,并确保所有写入路径统一经过该校验。

BEFORE UPDATE 必须校验状态跳转合法性
阻止非法状态回退(比如从 refunded 误退回到 paid)的关键,不是“拦截回退动作”,而是提前定义哪些跳转允许、哪些绝对禁止。MySQL 的 BEFORE UPDATE 触发器是唯一能掐住脖子的时机——此时数据还没落盘,OLD.status 和 NEW.status 都可读,且能用 SIGNAL 中断整个语句。
- 必须显式枚举合法路径,例如:
IF OLD.status = 'refunded' AND NEW.status != 'reversed' THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'refunded 只能转为 reversed'; - 禁用模糊否定逻辑,如
NOT IN ('draft', 'reviewing')——漏写一个合法值就等于放行非法回退 - 状态字段建议用
ENUM或带CHECK约束的VARCHAR,防止拼写错误绕过校验(比如'paied'不会被OLD.status = 'paid'匹配)
别在触发器里自动“修正”状态值
看到 NEW.status 是非法值时,有人会想:SET NEW.status = OLD.status 就完事了。这比报错更危险——表面数据没变,实际业务意图被掩盖,审计日志里看不出异常操作,下游系统可能基于错误状态做决策。
-
BEFORE UPDATE中允许SET NEW.xxx = ...仅限于**预设字段修正**(如标准化大小写、补默认时间),但状态机跳转必须由上游明确发起 - 任何“悄悄覆盖用户输入”的行为,都会让触发器失去可追溯性:你无法区分是应用主动改状态,还是触发器被动兜底
- 真要兼容旧逻辑,应在应用层加中间状态(如
pending_refund_reversal),而不是在触发器里做状态映射
AFTER UPDATE 不能用于阻止状态回退
AFTER UPDATE 触发器根本拦不住非法回退——NEW.status 已写入磁盘,哪怕你立刻 UPDATE 回去,也属于二次修改,违反原子性和事务一致性。更糟的是,如果回退操作本身失败(比如外键冲突),AFTER 里的修复逻辑可能让问题更难排查。
- 所有状态校验必须放在
BEFORE UPDATE,这是 MySQL 引擎保证的最后防线 -
AFTER UPDATE唯一合理用途是异步审计:写日志、发消息、更新统计表,但绝不能反向修改原记录 - 若业务要求“回退需审批”,应把审批流程前置到应用层,触发器只负责校验审批通过后的最终状态变更
容易忽略的权限与执行上下文陷阱
即使触发器逻辑完美,也可能因权限配置失效:运维脚本直连数据库、ETL 工具用高权限账号批量导入、甚至 DBA 临时关闭触发器,都会让状态校验形同虚设。
- 检查触发器是否启用:
SELECT name, is_disabled FROM information_schema.TRIGGERS WHERE EVENT_OBJECT_TABLE = 'your_table'; - 回收用户对基表的直接
UPDATE权限,只授予执行存储过程或调用 API 的权限 - 避免使用
DEFINER = 'root'@'%',改用专用低权限账号定义触发器,并确保该账号仅有必要权限(如只允许 INSERT 到日志表)
真正难的不是写几行 SIGNAL,而是确保所有数据写入路径——包括备份恢复脚本、跨库同步工具、旧版管理后台——都经过同一套状态校验入口。只要有一条路绕开触发器,状态机就崩了。











