ocean

牽涉到三個核心風險:

  • 超賣(Overselling)
  • 鎖競爭(Lock Contention)
  • 效能瓶頸(Performance Bottleneck)

庫存扣減要即時又要安全,但越嚴格的隔離會越慢,要怎麼平衡?

在扣庫存的交易中,最怕的不是「讀到舊資料」,而是「兩個人同時看到同一個庫存,然後都扣成功」。這其實不是 read 的問題,而是 concurrent update 的問題

關鍵是交易期間必須鎖定庫存那一筆資料,確保扣減是序列化的(serial)

通常在電商系統,我們會選擇交易層級用 READ COMMITTED 或 REPEATABLE READ,配合「明確加鎖查詢」

模式 隔離級別 特性 是否會鎖住那筆庫存 備註
(1) READ COMMITTED + UPDLOCK 預設 防髒讀 ✅ 是 最常用、安全
(2) SERIALIZABLE 最嚴格 防髒讀 + 幻讀 ✅ 是 (範圍鎖) 太重,僅用於財務對帳
(3) SNAPSHOT / RCSI 基於版本 不會 block,但需要行版本控制 ❌ 否 不適合庫存扣減(可能超賣)

範例一:扣庫存交易(單筆商品)

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN TRANSACTION;

– 1️⃣ 讀取並鎖定這筆庫存資料
SELECT stock
FROM inventory WITH (UPDLOCK, ROWLOCK, HOLDLOCK)
WHERE product_id = 1001;

– 2️⃣ 檢查庫存足夠再扣減
UPDATE inventory
SET stock = stock - 1
WHERE product_id = 1001;

COMMIT TRANSACTION;

  • UPDLOCK:取得更新鎖,避免其他人也拿到讀鎖後再轉更新鎖(防止死鎖)
  • ROWLOCK:明確要求行鎖(避免升級成頁鎖或表鎖)
  • HOLDLOCK:等同於 SERIALIZABLE 在這筆資料的效果(範圍鎖),直到交易結束才釋放

範例二:檢查庫存不足時回滾

BEGIN TRANSACTION;

DECLARE @stock INT;

SELECT @stock = stock
FROM inventory WITH (UPDLOCK, ROWLOCK, HOLDLOCK)
WHERE product_id = 1001;

IF @stock < 1
BEGIN
ROLLBACK TRANSACTION;
PRINT ‘Insufficient stock’;
RETURN;
END

UPDATE inventory
SET stock = stock - 1
WHERE product_id = 1001;

COMMIT TRANSACTION;

多人同時下單 → 只有第一個交易能鎖住那筆資料

其他交易必須等該交易 COMMIT 或 ROLLBACK

不會發生超賣