モジュール 11 · レッスン 2 / 4コース内の 42/57~10 min
モジュールのレッスン (2/4)
競合状態
単一の端末上でデータベースがどのように独立して動作するかを理解するのは簡単ですが、実際の Web シナリオでは、リクエストを送信する「Node/PHP/Go」プロセスが数百個も同時に存在することになります。ここで有名なレースコンディションが生まれます。
「アップデートが失われる」問題
更新の喪失は、2 つのスレッドが同じ古い行を読み取り、JavaScript サーバーで計算を行った後、結果を保存するために UPDATE を送り返すときに発生します。
シナリオ:
- 製品 #5 には 10 個のユニットがあります。
- ユーザー A が購入します (サーバー A は「10」を読み取り、「9」を計算します)。
- 同じミリ秒内に、A の購入が COMMIT フェーズを完了する前に、ユーザー B が別の商品を購入します。
- サーバー B も「10」を読み取り、「9」を計算します。
- サーバー A は
UPDATE: set quantity = 9を送信します。 - サーバー B は
UPDATE: set quantity = 9を送信します。 - 両方とも、数値が正常に減少したと考えています。しかし、最終的な値は 8 単位になるはずです。幻の購入品が紛失してしまいました。
大量のトランザクションを使用しないソリューション (アトミック更新)
外部で読み取って計算を行うのではなく、常に同期される更新中に DB に計算を実行させます。
SQL
-- Wrong
UPDATE products SET stock = 9 WHERE id = 5;
-- Correct and natively thread-safe
UPDATE products SET stock = stock - 1 WHERE id = 5;より極端な異常 (ダーティ リードとファントム)
- ダーティ リード: COMMIT によってまだ確認されていない他の人の一時データを読み取りました (突然 ROLLBACK に移行して無効になる可能性があります)。
- ファントム読み取り: すべての行のスキャンを実行し、その間に 2 番目のスレッドが新しいレコードを挿入し、同じトランザクション中に別のスキャンを実行すると、魔法のように新しいレコードがどこからともなく現れ、計算された財務レポートが危険にさらされることがわかります。
運動#sql.m11.l2.e1
試行回数: 0読み込み中…
小さな文字列操作のために顧客レコードをアトミックに変更します。\n顧客の「市」の名前を変更してアスタリスクを追加するとします。 \nこれを行うには、id=10 の行の独自の値を利用し、「 - Old」を現在の値に連結します (postgres の || 連結パイプを使用します)。
エディターを読み込み中…
ヒントを表示
顧客を更新 SET city = city || ' - 古い' WHERE id = 10;
3 回の試行後に解決策が利用可能になります
運動#sql.m11.l2.e2
試行回数: 0読み込み中…
古典的な更新の欠落は、支払いと価格で発生します。\n緊急の価格引き上げがあったとします。「order_items」テーブル (注文 1 の製品 1) の「unit_price」をアトミックに 20% 増加させます。つまり、JS で事前に手動で計算された新しい価格を挿入する代わりに、それに * 1.2 を掛けます。
エディターを読み込み中…
ヒントを表示
UPDATE order_items SET ユニット価格 = ユニット価格 * 1.2 WHERE 注文 ID = 1 AND 製品 ID = 1;
3 回の試行後に解決策が利用可能になります