メインコンテンツにスキップ
モジュール 11 · レッスン 3 / 4コース内の 43/57~15 min
モジュールのレッスン (3/4)

ロックと更新のための選択

単一の端末上でデータベースがどのように独立して動作するかを理解するのは簡単です...いや、もう言いました! 「アトミック更新」(stock = stock - 1 など) だけでは不十分な場合があります。

ユーザーが 50 ユーロ相当のものを購入するのに十分な残高があるかどうかを確認する必要があると想像してください。

  1. SELECT balance FROM users WHERE id = 1;
  2. JS: 残高が 50 未満の場合は、ブロックしてください。
  3. それ以外の場合は、チェックアウトに進みます...
  4. 推理を実行してください! (そして、ここでは、ステップ 2 で小切手を検証しているときに、別の並行小切手を発行してアカウントを焼き尽くしたユーザーが見つかり、今ではゼロを下回っています!)。

FOR UPDATE 明示的ロック

SELECT の最後に FOR UPDATE を追加すると、次の COMMIT まで、無害な読み取りが他のクライアントに対して封印されたロックに魔法のように変換されます。あなたがそれらの行をどうするかを決定するまで、他の誰も、それらの特定の選択された行に対して UPDATE (または更新のための同時読み取り) を実行することはできません。

SQL
BEGIN;

-- If id=3 had already been locked by T1, T2 will sit in perpetual loading on this "read"
-- frozen in place, before receiving the raw data, until T1 runs COMMIT freeing everyone!
SELECT balance
FROM users
WHERE id = 3
FOR UPDATE;

-- at this point our Node JS instance lives with the guarantee, knowing for certain that NO ONE ELSE in the world has
-- manipulated (nor been able to read with intent to alter) that balance while we complete the application logic

UPDATE users SET balance = balance - 50 WHERE id = 3;

COMMIT;

FOR SHARE もあります。これにより、他のリーダーは状態の読み取りをブロックせずに (ベース ロックの場合) 読み取りを続けることができますが、コミットするまでその後の更新はブロックされます。ただし、変更する場合は、常に排他的ロック句の総当りを使用してください。

自分で試してみてください

運動#sql.m11.l3.e1
試行回数: 0読み込み中…

通常の時間スコープのトランザクションを開き、製品 ID=5 の読み取りを排他的にロックします。\n2 段階のクエリを実行します。\n1.トランザクションを開きます (BEGIN)\n2. 「FOR UPDATE」オプションを付加することで、誰も変更しないことを保証しながら、「products」をクエリして ID 5 の行全体をフェッチします。

エディターを読み込み中…
ヒントを表示

BEGIN;、改行、SELECT * FROM products WHERE id = 5 FOR UPDATE;

3 回の試行後に解決策が利用可能になります

運動#sql.m11.l3.e2
試行回数: 0読み込み中…

場合によっては、トランザクションが悲惨な期間ハングすることがあります (デッドロックまたは無限待機)。\nこれを防ぐには、行がすぐに利用できなくなった瞬間にデータベースを中止するように命令できます。最後に NOWAIT を使用すると、SQL サーバー例外が JS にバウンスして、永久にハングすることなく try-catch されます。\nカテゴリ ID=1 に対して SELECT を実行し、FOR UPDATE を使用しますが、NOWAIT を強制します。

エディターを読み込み中…
ヒントを表示

SELECT * FROM category WHERE id = 1 FOR UPDATE NOWAIT;

3 回の試行後に解決策が利用可能になります