庫存超賣怎麼防?高併發下單的技術解法
限量 100 組的商品,開賣十分鐘後台出現 108 張付款成功的訂單——這就是超賣。接下來的劇本大家都熟:客服逐一道歉、退款、送補償券,運氣不好還上社群被公審。最冤的是,超賣通常不是行銷部多賣了,而是系統在高併發下單時「算錯了」。
這篇用白話把超賣的技術根因講清楚,再給幾個由淺入深的解法。看懂原理,你至少能問出正確的問題,判斷自家系統或外包廠商有沒有把這件事做對。
超賣的根因:先查再扣的時間差
最直覺的庫存邏輯是兩步:先查庫存夠不夠,夠的話再扣掉。單人下單毫無問題,但開賣瞬間幾百人同時進來,悲劇就發生了:剩最後 1 件時,A 和 B 幾乎同時查詢,系統對兩個人都說「有貨」,然後兩個人都扣款成功。這在工程上叫競態條件(race condition)——「檢查」和「動作」之間存在時間差,而高併發就是專門鑽這個時間差的。
所以超賣不是流量太大「把系統打壞了」,是邏輯本來就有洞,平常流量低洞踩不到而已。任何做過搶購活動的系統,都必須假設這個洞存在。
解法一:資料庫原子扣減(九成場景的正解)
把「查」和「扣」合併成一個不可分割的動作:直接對資料庫下「在庫存仍大於等於購買量的條件下扣減」的原子更新,資料庫保證同一時間只有一個請求成功修改同一列。扣減失敗就回覆售完,乾脆俐落。搭配資料庫交易與適當的鎖,這一招就能擋掉絕大多數電商的超賣場景——包括我們自己每天在跑的訂單系統。它的好處是實作簡單、絕對正確;代價是所有人搶同一列資料,併發極高時資料庫會成為瓶頸。但誠實說,台灣多數電商的搶購量級,離這個瓶頸還很遠。
解法二:快取預扣與排隊(真的很大量才需要)
當量級大到資料庫扛不住(想像演唱會售票等級),業界的做法是往前擋:
- 快取層預扣:把庫存放進記憶體型的快取服務(如 Redis),利用其單執行緒特性做原子扣減,扛住每秒數萬次的請求,扣到的人才放行進入後續下單流程,資料庫稍後再同步。這就是「最終一致性」的白話版:當下先用最快的層把名額鎖住,正式帳稍後補齊,保證最後對得起來。
- 排隊機制:更前面一層,讓流量先進等候室,按順序放行。體驗上多等幾秒,換來的是系統不雪崩、順序有公平性。
要提醒的是:這些方案的複雜度是跳階的,快取與資料庫之間的同步、故障時的補償,每一項都是新的坑。不要為了每年一次的活動,把全年天天在跑的系統改複雜。評估點永遠是:你的尖峰併發,真的超過原子扣減能扛的量嗎?
別忘了:付款未完成的庫存要放回來
反方向的問題同樣常見——不是超賣,是「假性售完」:客人下單鎖了庫存卻沒付款,庫存被空單占住,真想買的人買不到。標準做法是給未付款訂單一個保留時限(例如信用卡 15 分鐘、超商代碼依繳費期限),逾時自動取消並釋放庫存。釋放邏輯要跟金流回調小心對接:客人在第 14 分 59 秒付款成功、系統第 15 分整釋放庫存,這種邊界要處理乾淨,不然就是收了錢沒貨的客訴。預購與現貨並行的場景,這套時限與釋放規則還要對兩個庫存池分別生效。
超賣不是流量問題,是正確性問題——流量只是把一直存在的邏輯漏洞,放大到你不得不面對而已。
如果你打算辦限量搶購,上線前最該做的一件事是併發測試:模擬幾百個同時下單的請求打進去,看最終庫存數字對不對。我們自己的系統每次改到庫存邏輯都會重跑這套測試。不確定自家系統體質的話,歡迎找我們聊聊,開賣前檢查永遠比開賣後道歉便宜。
這類問題,我們每天都在自己的產品上解
免費 30 分鐘線上諮詢・先釐清方向,不推銷・一個工作天內回覆