楽観的ロック

データの読み取り時にロックせず、更新時にバージョンを検証して競合を検出する排他制御

データベース排他制御

楽観的ロックとは

楽観的ロック (Optimistic Locking) は、データの読み取り時にロックを取得せず、更新時にバージョン番号を検証して競合を検出する排他制御である。「競合はめったに起きない」という楽観的な前提に基づく。

悲観的ロック vs 楽観的ロック

観点 悲観的ロック 楽観的ロック
ロックのタイミング 読み取り時 更新時 (検証のみ)
前提 競合が頻繁に起きる 競合はめったに起きない
スループット 低い (ロック待ち) 高い (ロックなし)
競合時の動作 待機 エラー → リトライ
デッドロック 発生する 発生しない

DynamoDB での楽観的ロック

// 1. アイテムを取得 (version を含む)
const item = await db.get({ TableName: 'products', Key: { id: 'P1' } });
// { id: 'P1', name: 'Widget', stock: 10, version: 3 }

// 2. 更新時に version を検証
await db.update({
  TableName: 'products',
  Key: { id: 'P1' },
  UpdateExpression: 'SET stock = :stock, version = :newVersion',
  ConditionExpression: 'version = :currentVersion',
  ExpressionAttributeValues: {
    ':stock': 9,
    ':newVersion': 4,
    ':currentVersion': 3,  // 読み取り時の version
  },
});
// version が 3 でなければ ConditionalCheckFailedException → リトライ

競合の検出

ユーザー A: 読み取り (version=3) → 更新 (version=34) ✅ 成功
ユーザー B: 読み取り (version=3) → 更新 (version=34) ❌ 失敗 (version は既に 4)
ユーザー B: 再読み取り (version=4) → 更新 (version=45) ✅ 成功

RDS での楽観的ロック

-- 更新時に version を検証
UPDATE products
SET stock = 9, version = version + 1
WHERE id = 'P1' AND version = 3;

-- 影響行数が 0 なら競合が発生 → リトライ

使い分け

ケース 推奨
読み取りが多く、競合が少ない 楽観的ロック
競合が頻繁に発生 悲観的ロック
DynamoDB 楽観的ロック (条件付き書き込み)
在庫管理 (高競合) アトミックカウンター (ADD stock :dec)

アトミックカウンター (楽観的ロック不要)

// version チェックなしで安全に減算
await db.update({
  TableName: 'products',
  Key: { id: 'P1' },
  UpdateExpression: 'ADD stock :dec',
  ConditionExpression: 'stock >= :dec',
  ExpressionAttributeValues: { ':dec': -1 },
});

実務での活用方法は関連書籍にも詳しい。

関連用語