メインコンテンツにスキップ
モジュール 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 回の試行後に解決策が利用可能になります