前言

如果說首頁、搜尋、商品頁代表的是電商平台的流量壓力,那麼從加入購物車到正式下單,代表的就是平台最敏感的交易壓力。因為這條鏈路不只影響使用者體驗,還直接牽動庫存是否正確、訂單是否重複、支付是否能接上、後續履約是否有依據。

很多電商系統一開始都把這件事想得太簡單:使用者按下結帳,系統檢查庫存、建立訂單、扣掉庫存,然後等待付款。這條描述在白板上看起來很直覺,但一旦碰到高併發、大促、秒殺、重試請求、支付逾時、跨倉庫存與後續取消退款,問題就會快速失控。

所以這篇要處理的核心問題是:購物車、庫存與訂單到底應該怎麼拆? 以及在超大型電商場景下,為什麼「先查庫存再扣庫存」這種直覺流程往往不夠用,甚至會直接把系統帶進超賣、錯單與補償地獄。

系列文章導航

  1. 從商品瀏覽到訂單履約,超大型電商平台到底怎麼拆
  2. 商品目錄、搜尋、推薦、快取與 CDN,為什麼電商本質上是讀流量主導的系統
  3. 購物車、庫存預留、下單流程與超賣治理(本篇)
  4. 支付、退款、帳務、對帳與交易一致性
  5. MySQL、PostgreSQL、DynamoDB、Redis、OpenSearch 與 S3,資料到底該放哪裡
  6. CloudFront、WAF、ALB、EKS、Aurora 與 Multi-Region,AWS 基礎設施如何規劃
  7. 黑五與大促場景下的限流、降級、觀測、災難復原與成本控制

購物車是使用者意圖,不是正式訂單

電商系統最常見的第一個混淆,是把購物車當成「還沒付款的訂單」。

這種想法在業務上看似合理,但在系統上很危險。因為購物車通常只是:

  • 使用者暫時感興趣的商品集合
  • 可能跨裝置同步的狀態
  • 可能長時間不結帳的暫存資料
  • 可能被刪除、替換、合併或失效的意圖資訊

換句話說,購物車的核心責任是幫使用者記住「我想買什麼」,而不是代表平台已經承諾「你一定買得到」。

這個界線很重要,因為它直接影響後面的庫存策略。如果你在使用者把商品加進購物車的瞬間就永久扣減庫存,平台很快就會被大量棄單與囤貨行為拖垮。反過來說,如果你完全不做預留,高峰時又很容易在結帳瞬間大量超賣。

庫存不是一個欄位,而是一組狀態

中小型系統常見的商品表設計是這樣:

  • stock_total
  • stock_available

但大型電商的庫存通常至少要拆成幾種不同視角:

  • 總庫存:物理上存在的數量。
  • 可售庫存:目前可對外販售的數量。
  • 預留庫存:已被結帳流程占住,但尚未完成付款或確認的數量。
  • 在途庫存:採購、調撥或退貨回倉途中。
  • 倉庫維度庫存:不同倉、不同地區、不同配送時效的可售量。

只要平台開始有跨倉、預購、第三方賣家、門市取貨或活動保留量,庫存就已經不是一個欄位能解釋清楚的東西。

為什麼「先查庫存再扣庫存」不夠用

最直覺的流程通常是:

  1. 使用者點結帳。
  2. 系統查庫存夠不夠。
  3. 如果夠,就建立訂單。
  4. 然後把庫存減掉。

這在低流量情境可能還能運作,但在大促或熱門商品情境下,很容易出現經典 race condition:

  • 很多請求同時查到同一批剩餘庫存。
  • 每個請求都認為自己可以下單。
  • 後面才發現總和超過實際可售量。

如果你完全依賴資料庫列鎖硬扛,確實有機會保住正確性,但你會立刻面對:

  • 熱門 SKU 成為單點寫入瓶頸
  • 大量交易等待鎖
  • p99 延遲飆高
  • 整條下單鏈路在活動期間變得不可預測

所以對超大型電商來說,關鍵不是只有「正確」,而是要在正確的前提下,讓熱點也能被有秩序地承接。

Reservation 是電商高併發庫存治理的常見解法

很多成熟平台不會在下單最前面就做最終扣減,而是先做一層庫存預留(reservation)

它的邏輯通常是:

  1. 使用者進入結帳流程。
  2. 系統針對商品或 SKU 嘗試建立短生命週期 reservation。
  3. reservation 成功後,才允許進入訂單建立與支付流程。
  4. 若支付成功或訂單正式確認,reservation 轉為實際扣減。
  5. 若逾時未付款或流程失敗,reservation 自動釋放。

這種做法的價值在於:

  • 把「短暫占住庫存」與「最終成交」切開。
  • 可以吸收支付等待時間,而不必立刻做永久扣減。
  • 對熱門商品可以加上更明確的容量控管。

但 reservation 不是魔法,它會帶來新的責任:

  • 逾時回收機制
  • 重試與冪等控制
  • reservation 漏釋放的補償任務
  • 對帳與修復流程

也就是說,它不是讓問題消失,而是把問題從同步鎖衝突,轉移到狀態治理與補償治理

一條比較合理的下單鏈路會長什麼樣子

sequenceDiagram
    participant User as 使用者
    participant Cart as Cart Service
    participant Order as Order Service
    participant Inventory as Inventory Service
    participant Payment as Payment Service

    User->>Cart: 確認購物車內容
    User->>Order: 建立訂單請求
    Order->>Inventory: 嘗試建立 reservation
    Inventory-->>Order: reservation 成功 / 失敗
    Order-->>User: 建立訂單與支付意圖
    User->>Payment: 完成付款
    Payment-->>Order: 支付成功通知
    Order->>Inventory: reservation 轉正式扣減
    Order-->>User: 訂單確認成功

這條流程的重點不是 sequence diagram 本身,而是它反映了兩個核心觀念:

  1. 訂單建立與最終庫存扣減之間,通常存在一段待確認狀態。
  2. 支付、庫存與訂單不是同一個欄位就能表達清楚的狀態機。

訂單其實是一個狀態機,而不是一筆靜態資料

大型電商的訂單很少只是 createdpaidshipped 這幾個簡單狀態。更常見的真實情況是:

  • OrderCreated
  • InventoryReservationPending
  • InventoryReserved
  • PaymentPending
  • Paid
  • Picking
  • Packed
  • Shipped
  • Delivered
  • Cancelled
  • RefundPending
  • Refunded

一旦你接受訂單是狀態機,而不是資料表的一行記錄,很多實作上的判斷就會更清楚:

  • API 重試時應該檢查狀態與 idempotency key,而不是盲目重做。
  • 補償任務要知道自己正在補哪一段狀態轉移。
  • 後台查詢與客服工具要能解釋「卡在哪一個步驟」。

秒殺、大促與熱門 SKU 為什麼最容易超賣

對超大型電商來說,平時能跑,不代表活動時能活下來。熱門活動會把大量流量壓到少數商品、少數價格與少數分區,這時候最常見的問題就是:

  • 同一個 SKU 成為熱點
  • 同一段庫存邏輯反覆被打
  • 大量請求同時進來,造成 reservation 與訂單建立暴增
  • 支付前後狀態分叉,造成待清理資料急遽增加

所以成熟的系統通常不只靠資料庫交易來保護,而會疊加:

  • 入口排隊或漏斗式接受機制
  • 對熱門商品獨立限流
  • 單人限購
  • reservation TTL 與過期清理
  • 後台補償與對帳任務

本質上,這是一個系統容量治理問題,不只是 SQL 寫法問題。

Idempotency 是高併發下單流程的基本配備

在大型電商中,你幾乎不能假設使用者只會按一次按鈕,也不能假設 App、瀏覽器、API gateway、支付方都不會重送。

所以至少以下幾個動作通常都需要 idempotency 設計:

  • 建立訂單
  • 建立 reservation
  • 建立支付意圖
  • 接收支付 callback
  • 取消訂單
  • 退款請求

這裡的關鍵不是單純「防止重複 insert」,而是要保證:同一個業務意圖被重送時,系統能回到同一個業務結果,而不是重新走一次完整副作用。

Saga 與補償不是選配,而是現實

一筆電商交易實際上會經過多個系統:購物車、庫存、訂單、支付、履約、通知。只要跨過服務邊界,你就很難再用單一資料庫 transaction 包住所有事情。

這時候比較務實的做法通常不是硬追求分散式兩階段提交,而是:

  • 讓每一段局部交易各自成功提交
  • 透過事件驅動串起後續流程
  • 若中途失敗,執行補償動作

例如:

  • 訂單建好,但 reservation 失敗,訂單應取消或標記失敗。
  • reservation 成功,但支付失敗,應釋放 reservation。
  • 支付成功,但履約異常,後續要進入客服或退款處理。

這也是為什麼大型電商不能只設計 happy path,而必須把 failure path 當作主線一起設計。

總結

從購物車到正式下單,超大型電商真正要處理的不是一條簡單 CRUD 流程,而是一條高併發、跨狀態、跨系統、需要補償與對帳的交易鏈路。

購物車代表的是使用者意圖,reservation 代表的是短暫資源占用,訂單代表的是正式業務承諾,支付代表的是外部金流結果,而庫存扣減則是整條鏈路最不能出錯的核心約束之一。只要這些責任沒有被清楚切開,系統就會在高峰時段迅速進入混亂。

下一篇我們會接著往下走,處理電商最容易被寫得過於簡化、但實際上極其關鍵的一段:支付、退款、帳務、對帳與交易一致性。