存储过程不适合实现工作流状态流转,因其缺乏事件上下文、无法处理并发冲突、禁止I/O操作、硬编码路径难维护,仅适合校验合法性、补全字段和轻量聚合。

不能在存储过程中用 SQL 逻辑实现工作流的状态流转——它不是干这个的。
为什么存储过程不适合做状态流转决策
存储过程没有事件上下文,无法知道「谁点了通过」「基于哪条审批规则」「当前是否满足前置条件」。它只能被动执行,而状态流转是主动决策行为。
- 并发时两个审批人同时提交,都查到
CurrentStage = 2,都执行UPDATE SET CurrentStage = 3,最终只有一条生效,另一条静默失败或覆盖 - MySQL 中若在存储过程里更新同一张表再查(比如先查
approval_steps再更新AuditFlow),会直接报ERROR 1442 - 硬编码转移路径(如
CASE WHEN status = 2 THEN 3)导致每次加 CFO 审批都要 DBA 改存储过程、发版、停服 - 无法发邮件、调 API、写 Kafka —— MySQL/PostgreSQL 的存储过程禁止 I/O;SQL Server 虽支持
xp_cmdshell,但生产环境基本禁用
存储过程能且仅能做的三件事
把它当数据库侧的「守门员」,不是「流程引擎」。所有操作必须是确定性、无副作用、不依赖外部状态的。
-
校验状态跃迁合法性:比如只允许
OLD.status = 'pending'且NEW.status = 'approved_by_mgr'才允许更新,否则SIGNAL SQLSTATE '45000' -
补全冗余字段:自动写入
updated_at、last_approver_id,避免应用层漏传;别在过程里查users.role判断权限——把approver_role字段冗余进approval_steps表 -
轻量聚合生成主表状态:从
approval_steps查最高通过级,写回AuditFlow.CurrentStage,但绝不触发下一步动作(比如不 INSERT 下一级待办)
触发器里调用存储过程更新状态?更危险
MySQL 触发器中调用存储过程去改状态,极易撞上 ERROR 1422 (HY000): Explicit or implicit commit is not allowed in stored function or trigger —— 尤其是误写 INSERT INTO log_table 这类带隐式提交的操作。
- 触发器职责必须严格限定为「原子性状态变更」:比如把
status从'draft'改成'pending_first_approval' - 所有跨系统动作(邮件、IM、回调 API)必须剥离到应用层监听状态变更事件;日志写入也必须和主表更新同事务,不能靠触发器单独 INSERT
- 别用
MAX(level)判断流程终点——应查配置表获取当前流程总级数,否则某级被跳过会导致状态卡死
真正该由应用层控制的几个关键点
状态流转不是数据库该扛的活,而是应用最该收口的地方。哪怕用最简方案,也要守住这几条线:
- 更新必须带
WHERE current_status = ?条件,执行后检查ROW_COUNT()是否为 1;为 0 说明状态已变,业务应拒绝并提示「操作冲突」 - 状态字段用
TINYINT而非ENUM或VARCHAR,注释写明含义:COMMENT '0:待提交,1:审核中,2:已通过,3:已拒绝' - 合法转移路径抽成配置或代码逻辑,比如建一张
state_transition(from_status,to_status,event,allowed)表,运行时查表校验 - 多级审批必须用关联表
approval_steps,而非在主表硬塞first_approver_id、second_approver_id字段
最容易被忽略的,是把「数据库能做什么」和「业务需要什么」混为一谈——存储过程能校验、能补字段、能聚合,但它没法替你做决定。一旦开始在过程里写 IF ... THEN UPDATE ... ELSE INSERT ... 这类带分支的流转逻辑,就已在技术债上签字了。

















