楽観的ロック
データの読み取り時にロックせず、更新時にバージョンを検証して競合を検出する排他制御
データベース排他制御
楽観的ロックとは
楽観的ロック (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=3→4) ✅ 成功
ユーザー B: 読み取り (version=3) → 更新 (version=3→4) ❌ 失敗 (version は既に 4)
ユーザー B: 再読み取り (version=4) → 更新 (version=4→5) ✅ 成功
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 },
});
実務での活用方法は関連書籍にも詳しい。