不能,触发器无法实现主从状态机的完整自动流转,仅能作为状态变更守门人校验合法性、补全字段或写日志;复杂流转须交由应用层或工作流引擎处理。

触发器能自动流转主从状态机吗?不能,但可以辅助
直接用 SQL 触发器实现「主从状态机」的完整自动流转是危险且不可靠的。触发器没有状态管理能力,无法跟踪跨行、跨事务的状态依赖,也不支持条件循环或外部事件等待。它只能响应单次 DML(如 INSERT / UPDATE),执行同步、短时、确定性逻辑。真正需要的是:用触发器做「状态变更的守门人」——校验合法性、补全派生字段、发起轻量级副作用(如写日志表),而把状态流转决策交给应用层或专用工作流引擎。
BEFORE UPDATE 触发器校验主从状态合法性
这是最安全、最常用的切入点。比如主表 orders 有 status,从表 order_items 要求「只有当主单为 'confirmed' 时,子项才允许设为 'shipped'」。这时在从表上建 BEFORE UPDATE 触发器:
CREATE TRIGGER check_item_shippable
BEFORE UPDATE ON order_items
FOR EACH ROW
WHEN (NEW.status = 'shipped')
BEGIN
SELECT CASE
WHEN (SELECT status FROM orders WHERE id = NEW.order_id) != 'confirmed'
THEN RAISE(ABORT, 'Cannot ship item: parent order not confirmed')
END;
END;
注意点:
- 必须用
SELECT ... FROM orders查主表,不能依赖NEW.order_id外键约束——外键只保存在性,不保业务状态 - SQLite 用
RAISE(ABORT, ...);PostgreSQL 用RAISE EXCEPTION;MySQL 需用SIGNAL SQLSTATE '45000' - 避免在触发器里做复杂 JOIN 或子查询,否则易锁表或拖慢主 DML
用触发器自动更新主表聚合状态(非“流转”,是“反映”)
主表状态常由从表汇总而来,例如:orders.status 应等于所有 order_items.status 的最大值。这不是“流转”,而是“快照同步”。可在从表 AFTER INSERT/UPDATE/DELETE 中触发:
CREATE TRIGGER sync_order_status
AFTER INSERT OR UPDATE OR DELETE ON order_items
FOR EACH ROW
BEGIN
UPDATE orders SET status = (
SELECT COALESCE(MAX(CASE
WHEN status = 'shipped' THEN 3
WHEN status = 'packed' THEN 2
WHEN status = 'confirmed' THEN 1
ELSE 0 END), 0)
FROM order_items
WHERE order_id = COALESCE(NEW.order_id, OLD.order_id)
) WHERE id = COALESCE(NEW.order_id, OLD.order_id);
END;
关键限制:
- 不能用
NEW和OLD同时为空(如 PostgreSQL 的AFTER触发器需区分操作类型),MySQL 8.0+ 支持WHEN子句过滤 - 聚合结果必须可确定:若从表状态互斥(如不能同时有
'shipped'和'cancelled'),才能用 MAX;否则需更精细逻辑 - 高并发下可能因多次触发导致主表被反复更新,建议加
WHERE status != new_computed_status条件避免无谓写入
为什么不要在触发器里调用存储过程或发消息?
常见误区是让触发器调用 send_notification() 或 start_workflow_job()。这会带来三类问题:
- 事务边界失控:通知发送成功但主事务回滚 → 状态不一致;或通知失败导致整个 DML 失败 → 业务阻塞
- 数据库耦合外部系统:触发器内连 Kafka、HTTP 或 Redis,使数据库承担不该有的网络/IO 负担
- 调试黑洞:错误堆栈藏在触发器深处,日志分散,无法重放或补偿
正确做法是:触发器只写一张轻量 outbox 表(含 event_type、payload、created_at),由独立消费者轮询处理。这样解耦、可重试、可观测。
状态机真正的“流转”逻辑——比如「支付成功 → 库存预占 → 发货确认 → 完成」——涉及异步、超时、人工干预、幂等重试,这些都超出 SQL 触发器的能力边界。把它塞进触发器,就像用螺丝刀拧螺母:能转几圈,但很快崩刃。










