3秒でわかる
注文の確定に合わせて在庫数を減らす更新処理。同じ商品に注文が同時に来ても数量がずれないよう、排他制御とセットで設計します。
もう少し詳しく
どういうものか
在庫減算は、注文が確定したときに商品の在庫数をその分だけ減らす更新処理です。単に引き算するだけに見えますが、注文の登録と在庫の更新が両方成功して初めて意味を持つため、ひとつのトランザクションにまとめる必要があります。加えて、同じ商品に複数の注文が同時に来ても数量が矛盾しないよう、データベース側の仕組みで守ります。
なぜ必要か
在庫の数え間違いは、売れないはずの商品を売ってしまう欠品事故に直結します。読み取った値をアプリ側で引き算して書き戻す作りだと、2件の注文がほぼ同時に来たときに、どちらも同じ元の値を読み、片方の減算が消えます。残り1個の商品が2人に売れる状態です。これを防ぐために、読み取りと更新を分けない書き方が要ります。
具体例
BEGIN;
-- 現在値を読んでから引くのではなく、条件付きで一気に減らす
UPDATE products
SET stock = stock - 2
WHERE id = 101 AND stock >= 2;
-- 更新件数が0なら在庫不足なので巻き戻す
INSERT INTO orders (product_id, qty) VALUES (101, 2);
<a href="/glossary/commit" class="text-primary font-medium underline underline-offset-2 hover:text-primary-dark">COMMIT</a>;アプリ側では、更新された行数を見て分岐します。
cur.execute("UPDATE products SET stock = stock - %s WHERE id = %s AND stock >= %s", (qty, pid, qty))
if cur.rowcount == 0:
conn.rollback()
raise OutOfStock(pid)つまずきやすいところ
SELECT で在庫を確認してからアプリ側で判定し、別の文で UPDATE する作りが最も多い誤りです。確認と更新の間に他の注文が入り込みます。条件を WHERE に入れて1文にするか、SELECT ... FOR UPDATE で行に鍵をかけます。もうひとつ、複数商品をまとめて減らすときに商品IDの順序がばらばらだと、2つの注文が互いの鍵を待つデッドロックになります。常にID順で処理すると避けられます。
覚え方
在庫は「読んでから引く」のではなく「条件付きで引く」ものです。判定をデータベースの側に置くのが要点です。キャンセルや返品で在庫を戻す処理も同じ考え方で、加算だけを別の作りにすると、あとから数が合わなくなります。
似た用語との違い
| 語 | 内容 |
|---|---|
| トランザクション | 複数の更新をまとめて成立させる単位 |
| 排他制御 | 同時実行で矛盾が起きないようにする仕組み |
| 引当 | 注文前に在庫を確保しておく処理 |