大事务必须按业务语义拆分,核心操作(如扣库存+创建订单+扣余额)须同事务,非核心操作走最终一致或异步;拆分后需配套补偿事务、幂等设计与一致性校验。

大事务必须拆分,但不能靠简单加 COMMIT 切开就完事——那样大概率导致中间态数据不一致,比如订单已创建但库存没扣、钱已扣但通知没发。关键在于:**用业务语义定义“可接受的中间状态”,再配合补偿或最终一致性机制**。
哪些操作绝对不能拆到不同事务里
同一笔资金变动的正反向操作(如扣款+记账)、同一业务实体的状态跃迁(如订单从 pending → paid),必须保留在同一个事务内。否则会出现“钱没了但订单没生成”这类原子性破坏。
-
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1和INSERT INTO financial_records必须同事务 —— 否则余额变更不可逆,财务对不上 -
SELECT ... FOR UPDATE锁住的库存行,必须和紧随其后的UPDATE products在同一事务 —— 跨事务锁会释放,导致超卖 - 涉及外键约束的级联写入(如先插
orders再插order_items),若中间COMMIT,后续失败会导致主表脏数据
按业务阶段拆分:识别天然断点
真正安全的拆分点,来自业务流程本身存在的“确认点”或“异步点”,不是技术强行切开。例如电商下单:
- 阶段一(强一致):
SELECT ... FOR UPDATE+ 扣库存 + 创建订单 + 扣余额 —— 这四步必须在一个事务,因为它们共同构成“交易成立”的最小原子单元 - 阶段二(最终一致):发通知、更新积分、写日志 —— 这些不影响核心资金和库存,失败可重试或走补偿,单独成事务
- 阶段三(异步解耦):调用物流系统、生成发票 —— 完全离线,用消息队列投递,不参与主事务
注意:user_points 表更新和 point_records 插入也应同事务,否则积分变多了但没记录,审计无法追溯。
补偿事务与幂等设计是拆分的前提
一旦拆开,就必须为每个子事务准备对应的补偿逻辑,且所有接口要支持幂等。没有补偿,拆分=埋雷。
- 订单创建成功但积分更新失败?需有
reconcile_order_points定时任务扫描漏单并补发 - 通知发送失败后重试,
notifications表必须有唯一索引(如(order_id, type)),避免重复插入 - 补偿操作本身也要事务化:比如回滚积分,得同时更新
user_points和写point_records类型为compensate
别指望靠 TRY-CATCH + ROLLBACK 解决跨事务问题——它只管当前事务,不管其他已提交的子事务。
拆分后的一致性验证不能只靠人眼
上线后必须跑一致性校验脚本,而不是等用户投诉。重点检查高频路径下的跨表数据对齐:
- 查所有
status = 'paid'的订单,是否每笔都对应一条financial_records.type = 'order_payment'且金额一致 - 统计
products.quantity总和,对比order_items中已发货部分的总消耗量,偏差超过阈值立即告警 - 每日凌晨用
SELECT COUNT(*) FROM orders LEFT JOIN point_records ON ... WHERE point_records.id IS NULL扫描缺失积分记录
最容易被忽略的是:补偿逻辑上线后没压测过真实失败场景,结果重试风暴打垮下游服务——拆分不是结束,而是把一致性保障从数据库层转移到了应用层,责任更重了。











