sql server触发器不能驱动复杂审批流,因其无法控制事务、缺乏before语义、不适合状态机跳转与并发控制,仅宜用于轻量日志或时间戳更新,状态变更和校验必须由应用层或存储过程统一处理。

SQL Server 触发器不能驱动复杂审批流——它连事务提交都不允许,更别说状态机跳转、并发控制和规则动态加载。
触发器里写 COMMIT 或 ROLLBACK 会直接报错
SQL Server 的 DML 触发器运行在父语句的事务上下文中,它自己不能开启或结束事务。一旦你在触发器里写 COMMIT TRANSACTION 或 ROLLBACK TRANSACTION,就会触发错误:The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION 或类似提示。
这是因为触发器不是独立事务单元,强行控制会破坏原子性。哪怕只是想“自动推进到下一环节”,也不能靠触发器内建事务来保证一致性。
- 所有状态变更必须由应用层或存储过程统一发起,触发器只做轻量响应
- 如果真需要记录日志或更新时间戳,可以用
AFTER UPDATE触发器对同一行非关键字段赋值(如SET updated_at = GETDATE()) - 千万别在触发器里调用另一个存储过程去执行
UPDATE下一状态——这等于把并发冲突和死锁风险直接塞进数据库核心路径
BEFORE 不可用,INSTEAD OF 又太重
SQL Server 没有 MySQL 那样的 BEFORE UPDATE 语义,也没有 PostgreSQL 的 NEW := NEW 可修改机制。它只有 AFTER 和 INSTEAD OF 两类。
INSTEAD OF 虽然能拦截并重写操作,但代价极高:你得手动重写整个 INSERT/UPDATE 逻辑,还要显式处理 INSERTED 和 DELETED 表,稍有遗漏就丢数据。它适合做视图更新代理,不适合做审批状态路由。
- 状态合法性校验(比如“不能从 pending 直接到 approved_by_ceo”)必须放在应用层或存储过程中,用带条件的
UPDATE ... WHERE status = 'xxx'语句实现 - 若坚持用触发器做检查,只能在
AFTER中查刚写入的值,再通过RAISERROR+ROLLBACK回滚整条语句——但这属于事后纠错,不是事前控制 -
INSTEAD OF触发器里若调用外部服务(如发邮件、调 HTTP),会严重拖慢主流程,且失败时难以补偿
多级审批状态查询容易误判最新记录
直接 SELECT TOP 1 * FROM approval_steps ORDER BY created_at DESC 查当前状态,在驳回返工、并行审批、跳过节点等场景下必然出错。比如财务驳回后流程回到部门经理,最新记录是驳回,但待办仍是部门审批。
真正有效的查询必须结合步骤顺序、驳回标记、前序完成状态三重判断:
- 用
NOT EXISTS子查询排除“前序未完成”的步骤(s2.step_order ) - 再用一个
NOT EXISTS排除“前序已被拒”的步骤(s3.status = 'rejected') - 最终按
step_order DESC取第一条,才是当前激活节点 - 这种嵌套逻辑不适合放进触发器——它不响应数据变更,只是读取视图;应作为只读报表查询或缓存字段来源
最常被忽略的一点:触发器无法感知业务事件语义。它不知道“用户点了‘通过’按钮”,只知道“某字段被 SET 成了 'approved_by_mgr'”。一旦有人绕过前端直连数据库执行 UPDATE,整个审批链就失控。真正的控制点永远在应用层——那里才有角色、权限、上下文和重试逻辑。触发器顶多是个守门员,不是裁判员,更不是教练。










