MongoDB: トランザクション処理:ACIDとマルチドキュメント整合性

最終更新:2026-08-26

トランザクションはマルチドキュメント操作の原子性を保証します。この概念を習得することで、信頼性の高い金融・注文システムを開発できます。

1. 学習内容


100%
sequenceDiagram
    participant App as アプリケーション
    participant DB as MongoDB<br/>レプリカセット
    participant Log as ログ

    App->>DB: startTransaction()
    activate DB
    DB-->>App: session

    App->>DB: 100ドル引き落とし
    DB-->>App: OK
    App->>DB: 100ドル入金
    DB-->>App: OK
    App->>DB: 取引ログ作成
    DB-->>App: OK

    alt 全て成功
        App->>DB: commitTransaction()
        DB-->>App: ✅ コミット完了
        DB->>Log: 永続化
    else いずれか失敗
        App->>DB: abortTransaction()
        DB-->>App: ❌ ロールバック
        Note over DB: 全ての変更が取り消される<br/>データはトランザクション前の状態に復元
    end

    deactivate DB

2. なぜトランザクションが必要なのか

概念説明: トランザクションは、データベース操作の論理的な単位であり、その中の全ての操作が全体として成功するか、全体としてロールバックされることを保証します。複数のドキュメントやコレクションへの書き込み(資金移動や注文確定など)において、トランザクションなしではデータ整合性を保証できません。一部成功・一部失敗という状況はデータ汚染を引き起こします。

動作原理: MongoDB 4.0以降はマルチドキュメントACIDトランザクションをサポートしており、WiredTigerエンジンベースのスナップショット分離で実装されています。トランザクション開始時にスナップショットが作成され、全ての読み書き操作はそのスナップショットに対して実行されます。コミット時に変更が原子的にデータファイルに適用され、ロールバック時は全変更が破棄されます。内部的には、レプリカセットのoplogによって永続性とレプリケーションが保証されます。

ACID原則の詳細解説:

特性 意味 MongoDBでの実装 原理
原子性(Atomicity) トランザクションは全て成功か全て失敗 commit / abort WiredTigerはロールバックログで保証:コミット時は原子的書き込み、ロールバック時はログで復元
整合性(Consistency) データ整合性制約が維持される スキーマ検証 + トランザクション制約 トランザクションコミット前に全制約をチェック、違反があれば拒否
分離性(Isolation) 同時実行トランザクションが互いに干渉しない スナップショット分離 トランザクション開始時にデータスナップショット取得、同一トランザクション内では一貫した状態を維持
永続性(Durability) コミット後は永続保存 Journal + レプリカセットoplog 書き込みはまずjournal(WAL)に記録され、その後レプリカセットの過半数に複製

MVCCメカニズム: MongoDBはMulti-Version Concurrency Control(MVCC)でスナップショット分離を実装しています。各ドキュメントは複数の歴史バージョンを保持し、トランザクションは開始タイムスタンプに対応するバージョンからデータを読み取り、書き込み操作は古いバージョンを上書きせず新しいバージョンを作成します。コミット時にのみ新しいバージョンが他のトランザクションから可視になります。

100%
graph TB
    subgraph "MVCCマルチバージョン"
        D1["ドキュメント v1<br/>balance: 1000"]
        D2["ドキュメント v2<br/>balance: 900<br/>(トランザクションA編集)"]
        D3["ドキュメント v3<br/>balance: 1100<br/>(トランザクションB編集)"]
    end

    subgraph "トランザクションスナップショット読み取り"
        T1["トランザクションA (t1)<br/>v1を読む"] --> R1["balance: 1000"]
        T2["トランザクションB (t2)<br/>v1を読む"] --> R2["balance: 1000"]
    end

    D1 --> D2
    D1 --> D3

    style D2 fill:#cce5ff
    style D3 fill:#d4edda

使用シーン:

JAVASCRIPT
// ❌ 反例:トランザクションなしの送金
async function transfer(fromUserId, toUserId, amount) {
  await User.updateOne({ _id: fromUserId }, { $inc: { balance: -amount } });
  // システムクラッシュ!
  await User.updateOne({ _id: toUserId }, { $inc: { balance: amount } });
  // 送金元の残高は減ったが、送金先に入金されていない
}


3. トランザクションの基本使用

概念説明: MongoDBトランザクションはSessionオブジェクトで管理します。startSession()でセッションを作成、startTransaction()でトランザクション開始、commitTransaction()でコミット、abortTransaction()でロールバックします。トランザクション内の全操作に{ session }パラメータを渡す必要があります。

動作原理: トランザクションの完全なライフサイクルは:セッション開始 → トランザクション開始 → 操作実行(sessionパラメータ付き) → コミット/ロールバック → セッション終了。コミット時、WiredTigerは全変更をjournalに原子的に書き込み、ロールバック時はロールバックログで全変更を復元します。デフォルトのトランザクションタイムアウトは60秒で、タイムアウトすると自動的にロールバックされます。

トランザクションライフサイクル:

100%
stateDiagram-v2
    [*] --> StartSession: startSession()
    StartSession --> Active: startTransaction()
    Active --> Active: 操作を実行(session付き)
    Active --> Committed: commitTransaction()
    Active --> Aborted: abortTransaction()
    Committed --> [*]: endSession()
    Aborted --> [*]: endSession()

    note right of Active: デフォルト60秒で<br/>タイムアウト自動ロールバック
    note right of Committed: 変更をjournalに永続化

構文ルール:

ステップ メソッド 説明
1 startSession() セッション作成
2 startTransaction() トランザクション開始
3 操作 + {session} 全読み書き操作にsessionを渡す
4a commitTransaction() 全成功 → コミット
4b abortTransaction() いずれか失敗 → ロールバック
5 endSession() セッションリソース解放
JAVASCRIPT
// === MongoDB 4.0+ マルチドキュメントトランザクション ===
const session = db.getMongo().startSession();

session.startTransaction();
try {
  // 1. 引き落とし
  db.users.updateOne(
    { _id: fromUserId },
    { $inc: { balance: -amount } },
    { session }
  );

  // 2. 入金
  db.users.updateOne(
    { _id: toUserId },
    { $inc: { balance: amount } },
    { session }
  );

  // 3. トランザクションコミット
  await session.commitTransaction();
} catch (err) {
  // 4. ロールバック
  await session.abortTransaction();
  throw err;
} finally {
  session.endSession();
}

ポイント解説:

  1. { session }を渡し忘れた操作はトランザクションに含まれず、保護されません。
  2. commitTransactionabortTransactionは冪等操作で、繰り返し呼び出してもエラーになりません。
  3. トランザクションはタイムアウトで自動ロールバックされるため、アプリケーション層で適切なタイムアウトとリトライロジックを実装すべきです。


4. ACID特性

概念概要: ACIDはデータベーストランザクションの4つのコア保証を指します—原子性、整合性、分離性、永続性。MongoDBでACIDがどう実装されているかを理解することは、信頼性の高いトランザクションシステム設計の基礎です。

分離レベルの詳細: MongoDBは3つの読み取り分離レベルをサポートし、readConcernで制御します:

分離レベル readConcern 動作 使用シーン
未コミット読み取り local 最新のローカルデータを読む(ロールバックの可能性) デフォルト、パフォーマンス優先
コミット済み読み取り majority 過半数で確認されたデータを読む 強整合性が必要な場合
スナップショット分離 snapshot トランザクション内で一貫したスナップショット トランザクション内デフォルト
100%
sequenceDiagram
    participant T1 as トランザクション1
    participant T2 as トランザクション2
    participant DB as MongoDB

    Note over DB: 初期残高=1000

    T1->>DB: startTransaction(readConcern: snapshot)
    T1->>DB: 残高を読む → 1000

    T2->>DB: startTransaction()
    T2->>DB: 残高 -100 → 900を書き込み
    T2->>DB: commitTransaction()

    T1->>DB: 残高を読む → 1000(スナップショット分離、T2の変更は見えない)

    Note over T1: スナップショットがトランザクション内整合性を保証

    T1->>DB: commitTransaction()
    Note over DB: 競合検出 → T1も残高を変更している場合、エラー発生
特性 意味 MongoDB実装
原子性 トランザクションは全成功か全失敗 commit / abort
整合性 データ整合性制約 スキーマ検証 + トランザクション
分離性 同時実行トランザクションが干渉しない スナップショット分離
永続性 コミット後は永続保存 Journal + レプリカセット


5. readConcern / writeConcern / readPreference

概念説明: この3つの設定はMongoDBトランザクション整合性制御の「三位一体」であり、それぞれ読み取り整合性、書き込み永続性、読み取りルーティングポリシーを制御します。これらがトランザクションの整合性とパフォーマンスのトレードオフを決定します。

動作原理:

(1) 書き込み確認(Write Concern)

原理: 書き込み操作がPrimaryに書き込まれた後、Secondaryから指定された数の確認を受けてから成功を返します。

パラメータ 動作 整合性 パフォーマンス
w 1 Primary確認のみ 低い 最速
w majority 過半数ノード確認 高い 遅い
j true ディスクjournalに書き込み 最も安全 最も遅い
wtimeout ms 待機タイムアウト タイムアウトエラー
JAVASCRIPT
session.startTransaction({
  writeConcern: {
    w: 'majority',         // 過半数ノードで確認
    j: true,               // ディスクjournalに書き込み
    wtimeout: 5000         // 5秒タイムアウト
  }
});

(2) 読み取り確認(Read Concern)

原理: 読み取り操作がアクセスできるデータバージョンを制御—最新のローカルバージョン(コミットされていない可能性)か、過半数確認済みバージョン(コミット済み)か。

レベル 説明 適用シーン
local 最新のローカルデータを読む(デフォルト) パフォーマンス優先、未コミットデータの読み取りを許容
majority 過半数確認済みデータを読む 強整合性が必要
snapshot スナップショット分離(トランザクション内のみ) トランザクションデフォルト、ファジーリード防止
JAVASCRIPT
session.startTransaction({
  readConcern: {
    level: 'majority'      // コミット済みデータを読む
  },
  writeConcern: { w: 'majority' }
});

(3) 読み取り設定(Read Preference)

原理: 読み取り操作をPrimaryかSecondaryノードにルーティングするかを制御し、読み書き分離を実現します。

モード 動作 適用シーン
primary プライマリノードのみ読み取り 強整合性トランザクション
primaryPreferred プライマリ優先、不可ならセカンダリ 一般的なシーン
secondary セカンダリノードのみ読み取り レポート/分析、プライマリ負荷軽減
secondaryPreferred セカンダリ優先、不可ならプライマリ 読み取り多・書き込み少
nearest ネットワーク遅延最小 地理分散クラスター
JAVASCRIPT
session.startTransaction({
  readPreference: 'primary'             // プライマリノードのみ読み取り
});

session.startTransaction({
  readPreference: 'secondary'           // セカンダリから読み取り
});

session.startTransaction({
  readPreference: 'secondaryPreferred'  // セカンダリ優先
});

推奨3点セット:

シーン writeConcern readConcern readPreference
金融取引 majority + j:true snapshot primary
一般トランザクション majority majority primary
レポート分析 local secondary
開発・テスト w:1 local primaryPreferred


6. Mongooseトランザクションカプセル化

概念説明: MongooseはよりエレガントなトランザクションAPIを提供—startSession()sessionパラメータ。ただし、手動でトランザクションを管理するtry-catch-commit-abortの定型コードは冗長です。withTransactionユーティリティ関数にカプセル化することで、ビジネスロジックコードを大幅に簡素化できます。

動作原理: MongooseのwithTransactionラッパーは、セッションライフサイクル管理(開始 → コミット/ロールバック → 終了)を自動化し、ビジネス関数はコアロジックに集中できます。MongooseはModel操作(findByIdcreateupdateOne)に{ session }パラメータを渡すことをサポートし、CRUD操作をトランザクション内でシームレスに統合できます。

トランザクションカプセル化パターンの比較:

パターン コード量 エラー処理 リトライ対応 使用シーン
手動try-catch 多い 手動 なし 単純なシーン
withTransactionカプセル化 少ない 自動 追加可能 本番環境推奨
mongoose.connection.transaction 最小 自動 組み込み Mongoose 6+
JAVASCRIPT
// === Mongooseトランザクションカプセル化 ===
async function withTransaction(callback) {
  const session = await mongoose.startSession();
  session.startTransaction();
  try {
    const result = await callback(session);
    await session.commitTransaction();
    return result;
  } catch (err) {
    await session.abortTransaction();
    throw err;
  } finally {
    session.endSession();
  }
}

// === 使用例:送金 ===
async function transfer(fromUserId, toUserId, amount) {
  return withTransaction(async (session) => {
    const fromUser = await User.findById(fromUserId).session(session);
    if (fromUser.balance < amount) {
      throw new Error('残高不足');
    }

    await User.updateOne(
      { _id: fromUserId },
      { $inc: { balance: -amount } },
      { session }
    );

    await User.updateOne(
      { _id: toUserId },
      { $inc: { balance: amount } },
      { session }
    );

    await TransactionLog.create([{
      fromUserId,
      toUserId,
      amount,
      createdAt: new Date()
    }], { session });

    return { success: true };
  });
}

▶ サンプル 2:Mongooseトランザクションリトライカプセル化

JAVASCRIPT
// AliceのShopHub金融システム:トランザクションでWriteConflictに遭遇したら自動リトライ
async function withRetryTransaction(callback, maxRetries = 3) {
  let lastError;
  for (let i = 0; i < maxRetries; i++) {
    const session = await mongoose.startSession();
    session.startTransaction({
      readConcern: { level: 'snapshot' },
      writeConcern: { w: 'majority' }
    });
    try {
      const result = await callback(session);
      await session.commitTransaction();
      return result;
    } catch (err) {
      await session.abortTransaction();
      lastError = err;
      if (err.errorLabels && err.errorLabels.includes('TransientTransactionError')) {
        console.log(`WriteConflictによりリトライ ${i + 1}/${maxRetries}`);
        continue;
      }
      throw err;
    } finally {
      session.endSession();
    }
  }
  throw lastError;
}

// 使用例
await withRetryTransaction(async (session) => {
  await User.updateOne({ _id: fromId }, { $inc: { balance: -100 } }, { session });
  await User.updateOne({ _id: toId }, { $inc: { balance: 100 } }, { session });
});


7. トランザクションの制限

概念説明: MongoDBトランザクションには明確な使用境界があります—レプリカセット内で実行する必要があり、サイズ制限があり、一部の操作はサポートしていません。これらの制限を理解することが本番環境でのトラブル回避の鍵です。

制限の詳細:

制約 説明 理由 軽減策
レプリカセット必須 スタンドアロンMongoDBはトランザクション未対応 トランザクションはoplogに依存 開発環境では単一ノードレプリカセットを許可
16MBドキュメント トランザクション内全操作の合計 WiredTiger単一ドキュメント制限 大きなトランザクションを分割
デフォルト60秒タイムアウト maxTransactionLockRequestTimeoutMillis 長時間トランザクションのロック保持防止 タイムアウトパラメータ調整
Cappedコレクション操作不可 一部制限 Cappedコレクションはロールバック未対応 トランザクション内でCappedコレクションを避ける
トランザクション内コレクション作成不可 一部制限(4.4+で緩和) DDLとトランザクションの競合 トランザクション前にコレクション作成
書き込み競合 同一ドキュメントへの同時変更 楽観ロック機構 TransientTransactionErrorを自動リトライ
ロック待機 長時間トランザクションが他操作をブロック 意図書き込みロック トランザクションを短くし、時間のかかる操作を避ける

トランザクションパフォーマンスへの影響:

操作 非トランザクション トランザクション内 オーバーヘッド理由
単一ドキュメント書き込み ベースライン +30–50% スナップショット維持 + ロック管理
マルチドキュメント書き込み N回の独立I/O 1回のコミット トランザクションを1回のI/Oにマージ、実際には高速な場合も
読み取り ベースライン +10–20% スナップショット読み取りの追加オーバーヘッド
コミット 5–50 ms journal fsync + oplog書き込み

トランザクションのベストプラクティス:

プラクティス 説明
トランザクションは可能な限り短く ロックを保持する長時間トランザクションを避ける、100ms以内に
トランザクション内での計算を避ける 複雑な計算はトランザクション外で実行、トランザクションは読み書きのみに
WriteConflictをリトライ MongoDB 4.0+はerrorLabelsでリトライ可能エラーを識別
単一ドキュメント原子操作を優先 updateOne + $incはそれ自体で原子性あり、トランザクション不要


8. 因果整合性(Causal Consistency)

概念説明: 因果整合性は強整合性より軽量な整合性モデルです。全ての操作がグローバルに順序付けられることは保証しませんが、因果関係のある操作が正しい順序で実行されることは保証します。例えば、「残高を読んでから引き落とす」場合、引き落とし操作は直前に読んだ残高に基づく必要があります。これが因果依存関係です。

動作原理: MongoDBはoperationTimeclusterTimeで因果整合性を実現します。セッション内の各操作は前の操作のlogicalTimeを持ち、サーバーは後続の操作が先行操作の結果を見ることを保証します。因果整合性を有効にするにはreadConcern: majority + writeConcern: majorityが必要です。

因果整合性と他の整合性モデルの比較:

モデル 保証 パフォーマンス 適用性
強整合性(線形化可能性) グローバル順序 最も遅い 金融コア
因果整合性 因果順序 比較的速い マルチステップ操作
結果整合性 順序なし 最も速い ログ、通知
自分の書き込みが見える 自分の書き込みは可視 速い ユーザー体験
100%
sequenceDiagram
    participant A as Alice
    participant P as Primary
    participant S as Secondary

    A->>P: 残高を読む(readConcern: majority)
    P-->>A: balance=1000, clusterTime=t1

    A->>P: 100ドル引き落とし(writeConcern: majority)
    Note over A,P: afterClusterTime=t1を付与
    P->>S: oplog複製
    S-->>P: 確認
    P-->>A: OK, clusterTime=t2

    A->>P: 取引履歴を見る(readConcern: majority)
    Note over A,P: afterClusterTime=t2を付与
    P-->>A: 直前の引き落とし記録を含む ✅

    Note over A,P: 因果整合性:読んだら、自分の書き込みは必ず認識できる
JAVASCRIPT
// === 因果整合性:操作の正しい順序を保証 ===
const session = db.getMongo().startSession();
session.startTransaction({
  readConcern: { level: 'majority' },
  writeConcern: { w: 'majority' }
});

// 操作1:現在の残高を読む
const account = db.accounts.findOne({ userId: 'user_001' }, { session });
// 操作2:読み取り結果に基づいて書き込み
db.accounts.updateOne(
  { userId: 'user_001' },
  { $set: { balance: account.balance - 100 } },
  { session }
);
// 保証:操作2は操作1の後の状態を見る

ポイント解説:

  1. 因果整合性を保証するには、セッションを使用し、readConcernwriteConcernを両方majorityに設定する必要があります。
  2. ノードをまたいで読み取る(readPreference: secondary)場合、因果整合性はローカルノードが行った書き込みを反映した読み取りを保証します。
  3. 因果整合性は、MongoDBのマルチドキュメントトランザクションとChange Streamsの基盤メカニズムです。

▶ サンプル:EC注文トランザクションの完全なハンズオンガイド

JAVASCRIPT
// シーン:注文確定プロセス(注文 + 在庫減少 + ウォレット引き落とし + ログ記録)、完全に原子的
// 前提:レプリカセットが必要。トランザクションは実行中であること

// データ初期化
db.products.insertOne({ sku: 'PHONE-001', stock: 10, price: 599 });
db.users.insertOne({ _id: 'user_001', balance: 1000 });
db.transaction_logs.createIndex({ userId: 1, createdAt: -1 });

// 完全なトランザクション関数
async function placeOrder(userId, items) {
  const session = db.getMongo().startSession();
  session.startTransaction({
    readConcern: { level: 'snapshot' },
    writeConcern: { w: 'majority' }
  });

  try {
    // 1. 合計金額を計算 + 在庫チェック(原子的読み取り)
    let total = 0;
    for (const item of items) {
      const product = db.products.findOne(
        { sku: item.sku, stock: { $gte: item.qty } },
        { session }
      );
      if (!product) {
        throw new Error(`在庫切れ: ${item.sku}`);
      }
      total += product.price * item.qty;
    }

    // 2. ユーザー残高チェック
    const user = db.users.findOne({ _id: userId }, { session });
    if (user.balance < total) {
      throw new Error('残高不足');
    }

    // 3. 在庫減少(条件付き、オーバーセール防止)
    for (const item of items) {
      const result = db.products.updateOne(
        { sku: item.sku, stock: { $gte: item.qty } },
        { $inc: { stock: -item.qty } },
        { session }
      );
      if (result.modifiedCount === 0) {
        throw new Error(`在庫減少失敗: ${item.sku}`);
      }
    }

    // 4. ユーザー残高から引き落とし
    db.users.updateOne(
      { _id: userId, balance: { $gte: total } },
      { $inc: { balance: -total } },
      { session }
    );

    // 5. 注文作成
    const orderResult = db.orders.insertOne({
      userId,
      items,
      total,
      status: 'paid',
      createdAt: new Date()
    }, { session });

    // 6. 取引ログを記録
    db.transaction_logs.insertOne({
      userId,
      orderId: orderResult.insertedId,
      amount: total,
      type: 'purchase',
      createdAt: new Date()
    }, { session });

    // 7. トランザクションコミット
    session.commitTransaction();
    return { success: true, orderId: orderResult.insertedId };

  } catch (err) {
    // いずれか失敗 → 全てロールバック
    session.abortTransaction();
    return { success: false, error: err.message };
  } finally {
    session.endSession();
  }
}

// 実行:注文確定
placeOrder('user_001', [
  { sku: 'PHONE-001', qty: 1 }
]);

// ロールバックシナリオのテスト:意図的にエラーを作成
placeOrder('user_001', [
  { sku: 'NONEXIST', qty: 1 }  // 商品が存在しない
]);
// 例外発生 → トランザクションロールバック → 在庫、残高、注文、ログ全て変更なし

// 原子性の検証:
// db.products.findOne({ sku: 'PHONE-001' }) → stock: 10(減少していない)
// db.users.findOne({ _id: 'user_001' }) → balance: 1000(引き落としなし)

出力:

TEXT 📖 参照専用
トランザクション成功時は全変更がまとめてコミット、失敗時は全変更がロールバックされ、データ整合性が保証される。

▶ サンプル 3:決済と在庫減少を伴うEC注文(難易度 ⭐⭐)

JAVASCRIPT
// シーン:ShopHubチェックアウト - 在庫予約、注文作成、決済引き落としを原子的に
const mongoose = require('mongoose');
const session = await mongoose.startSession();
session.startTransaction();

try {
  const Product = mongoose.model('Product');
  const Order = mongoose.model('Order');
  const User = mongoose.model('User');

  const userId = 'user_001';
  const items = [
    { productId: 'prod_001', sku: 'PHONE-001', qty: 2, price: 599 },
    { productId: 'prod_002', sku: 'BOOK-001', qty: 1, price: 29 }
  ];
  const totalAmount = 1227;

  // ステップ1:在庫チェックと予約(悲観ロック付き)
  for (const item of items) {
    const product = await Product.findOneAndUpdate(
      { _id: item.productId, stock: { $gte: item.qty } },
      { $inc: { stock: -item.qty } },
      { session, new: true }
    );
    if (!product) {
      throw new Error(`${item.sku}の在庫不足`);
    }
  }

  // ステップ2:ユーザー残高から引き落とし
  const user = await User.findOneAndUpdate(
    { _id: userId, balance: { $gte: totalAmount } },
    { $inc: { balance: -totalAmount } },
    { session, new: true }
  );
  if (!user) {
    throw new Error('残高不足');
  }

  // ステップ3:注文作成
  const order = await Order.create([{
    userId,
    items,
    total: totalAmount,
    status: 'paid',
    paidAt: new Date()
  }], { session });

  await session.commitTransaction();
  console.log('注文作成完了:', order[0]._id);
} catch (err) {
  await session.abortTransaction();
  console.error('トランザクション失敗:', err.message);
} finally {
  session.endSession();
}

出力:

TEXT 📖 参照専用
注文作成完了: 67890abcdef12345
// または失敗時:
トランザクション失敗: PHONE-001の在庫不足

❓ よくある質問

Q トランザクションはデータの強整合性を保証できますか?
A レプリカセット + writeConcern majority + readConcern majority + readPreference primaryで、可能です。
Q トランザクションのパフォーマンス劣化はどの程度ですか?
A 非トランザクションより30–50%遅くなります。トランザクションはロックとスナップショットを伴うためです。
Q 単一ノードMongoDBでトランザクションを使用できますか?
A いいえ。レプリカセットまたはシャードクラスターが必要です。

📖 まとめ


📝 練習問題

  1. 基礎問題(⭐): mongoshを使用して送金トランザクションを実装(try-catchロールバック付き)。
  2. 基礎問題(⭐): MongooseでwithTransactionユーティリティ関数をカプセル化。
  3. 応用問題(⭐⭐): 注文トランザクションを実装(注文確定 + 在庫減少 + 注文作成 + カートクリア—全て原子的)。
  4. 応用問題(⭐⭐): 失敗時のトランザクションロールバックをテスト(意図的にエラーを投げて原子性を検証)。
  5. チャレンジ(⭐⭐⭐): 分散ロールバック対応の完全なECトランザクションシステム(注文 + 在庫 + ウォレット + ログ)を構築。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%