#109 同じ商品が2人に売れた
師範 / 目安 35分
Issue #109 同じ商品が2人に売れた
報告者: 松田(事務局)
「事件だ。ワシが買うたはずの湯呑みが、別の人にも売れとる。二重販売じゃ」
コードを読むと、購入はこう書かれています。
JavaScript
const item = db.prepare("SELECT * FROM items WHERE id = ?").get(id);
if (item.sold === 1) { /* 売切れなら断る */ }
db.prepare("UPDATE items SET sold = 1 WHERE id = ?").run(id);
db.prepare("INSERT INTO purchases ...").run(id, buyer);一見すると正しく見えます。確認してから更新しているからです。
問題はその隙間です。確認と更新のあいだに、別の購入が割り込めます。
プレーンテキスト
松田さん 確認(まだ売れてない) ────→ 更新
陽葵さん 確認(まだ売れてない) ────→ 更新2人とも「まだ売れていない」を見て、2人とも更新します。湯呑みは1つしかないのに、購入記録は2件残ります。
直し方は2つを組み合わせます。
1つ目は、確認と更新を不可分にすることです。BEGIN から COMMIT までをひとまとまりとして扱い、途中で失敗したら ROLLBACK で全部戻します。「売切れにはなったが購入記録が無い」という中途半端な状態を残しません。
2つ目は、更新の条件で確かめることです。
SQL クエリ
UPDATE items SET sold = 1 WHERE id = ? AND sold = 0AND sold = 0 があると、すでに売れている行は更新されません。実際に何行変わったかは changes で分かります。0 なら誰かに先を越されたということです。
条件を WHERE に持ち込むと、確認と更新が1つの操作になります。隙間がなくなります。
すでに売れていたときは 409 です。商品は存在するので 404 ではなく、依頼の書き方も正しいので 400 でもありません。いまの状態と矛盾するという失敗です。
完成条件
POST /items/:id/purchase の競合を直してください。
購入できたときは 200 で {"id":1,"sold":1,"purchases":1} を返します。すでに売れていたときは 409 で すでに売り切れです、商品が無ければ 404 です。
同じ商品を2回買おうとしたら、2回目は必ず 409 になるようにしてください。
要件
UPDATE items SET sold = 1 WHERE id = ? AND sold = 0の形で、条件を更新に持ち込むchangesが 0 なら 409 ですでに売り切れですを返す- 売切れ化と購入記録を
BEGINからCOMMITまでで不可分にする
入出力例
request("POST", "/items/1/purchase", {"buyCount":1}) → "200 {"id":1,"sold":1,"purchases":1}"
request("POST", "/items/1/purchase", {"buyCount":2}) → "409 {"error":"すでに売り切れです"}"
request("POST", "/items/2/purchase", {"buyCount":3}) → "409 {"error":"すでに売り切れです"}"
request("POST", "/items/999/purchase", {"buyCount":1}) → "404 {"error":"商品が見つかりません"}"ヒント
前のヒントを開くと次が開きます。開かずに解けると未開封クリアです