构建可靠事务:幂等操作实战指南
多阶段事务通过幂等操作和状态日志,确保故障时数据完整性。这种方法适用于任何数据库,包括 MongoDB,能有效防止断电或系统崩溃导致的数据丢失。
多阶段事务工作原理
事务拆分为顺序阶段,每阶段记录在独立的 TRANSACTIONS 表中。主要状态包括:
- blocking — 锁定账户,防止并发操作。
- save_balance — 在日志中快照原始余额。
- main — 执行核心资金转账。
- unlock — 完成后台解锁账户。
- finished — 成功事务的最终状态。
幂等性确保故障后重复执行阶段不会破坏数据。例如,A := B + 1 是幂等的,而 A := A + 1 不是——但可拆分为两个幂等步骤。
带代码示例的实现步骤
假设从 Assange 账户转账 250 元给 Agent Y。ACCOUNTS 表初始数据:
| ID | NAME | BALANCE | TR_ID |
|----|----------|---------|-------|
| 34 | Assange | 300 | null |
| 78 | Agent Y | 7 | null |
1. 锁定账户 (STATUS='blocking')
先创建 TRANSACTIONS 记录:
| ID | FROM_ID | TO_ID | AMOUNT | FROM_BALANCE | TO_BALANCE | STATUS |
|----|---------|-------|--------|--------------|------------|----------|
| 172| 34 | 78 | 250 | null | null | blocking |
锁定使用原子命令,例如在 MongoDB 中:
db.accounts.updateOne(
{ ID: 34, TR_ID: null },
{ $set: { TR_ID: 172 } }
);
若无文档更新,则锁定失败——重试或中止。提前排序 ID 避免死锁。
2. 快照余额 (STATUS='save_balance')
锁定后,将原始余额复制到 TRANSACTIONS——设计上即幂等:
db.transactions.updateOne(
{ ID: 172 },
{ $set: { FROM_BALANCE: 300, TO_BALANCE: 7, STATUS: 'save_balance' } }
);
3. 执行转账 (STATUS='main')
核心操作利用保存的余额实现幂等:
db.accounts.updateOne(
{ ID: 34, TR_ID: 172 },
{ $set: { BALANCE: 50 } } // 300 - 250
);
db.accounts.updateOne(
{ ID: 78, TR_ID: 172 },
{ $set: { BALANCE: 257 } } // 7 + 250
);
变更生效后,才更新状态为 'unlock'。
4. 解锁并完成 (STATUS='unlock')
通过 TR_ID 检查释放锁:
db.accounts.updateOne(
{ ID: 34, TR_ID: 172 },
{ $set: { TR_ID: null } }
);
双账户解锁后,设置状态为 'finished'。
优势与局限
核心优势:
- 容错性:任何故障均可恢复,无数据丢失。
- 幂等性:安全重试操作,对分布式系统至关重要。
- 通用性:适用于各类数据库,不限于 MongoDB。
- 一致性:每个阶段均保障数据完整。
- 性能:额外日志可能略微降低速度。
缺点包括复杂度增加和日志开销。对于金融应用或关键系统,价值远超成本。
开发者Tips
- 利用数据库原子操作实现锁和更新。
- 为失败锁构建重试逻辑。
- 测试崩溃场景,如进程突发终止。
- 排序 ID 降低死锁风险。
此方案提供 2PC 级可靠性,同时保留 NoSQL 灵活性。
— Editorial Team
暂无评论。