入庫單表含 status 欄位記錄入庫狀態。業務邏輯:入庫前先查狀態,若為「未入庫」則執行入庫並更新狀態。併發風險:兩用戶同時查詢均獲「未入庫」,導致重複入庫,產生髒資料。
在存儲過程開啟事務時,以 FOR UPDATE 子句鎖定目標行,阻塞其他事務對同一行的修改操作,保證「查-改」原子性。
SELECT ... FOR UPDATE [OF column_list] [WAIT n | NOWAIT] [SKIP LOCKED];
ORA-30006,避免無限期阻塞。CREATE OR REPLACE PROCEDURE p_instock (p_dm IN VARCHAR2) IS
v_flag CHAR(1);
BEGIN
-- 1. 查詢並加行鎖,最多等待 5 秒
SELECT status INTO v_flag
FROM stock_table
WHERE dm = p_dm
FOR UPDATE WAIT 5;
-- 2. 業務判斷
IF v_flag = 'N' THEN
-- 執行入庫邏輯
UPDATE stock_table SET status = 'Y', instock_time = SYSDATE WHERE dm = p_dm;
COMMIT;
ELSE
-- 已入庫,拋出自訂異常或記錄日誌
RAISE_APPLICATION_ERROR(-20001, '重複入庫:單據 ' || p_dm || ' 已處理');
ROLLBACK;
END IF;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
RAISE;
END;
| 窗口 1 | 窗口 2 | 結果說明 |
|---|---|---|
SELECT * FROM t WHERE a='1' FOR UPDATE; | SELECT * FROM t WHERE a='1'; | 純查詢不受行鎖影響,立即返回 |
| (持有鎖) | SELECT * FROM t WHERE a='1' FOR UPDATE; | 阻塞等待,直到窗口 1 COMMIT/ROLLBACK |
| (持有鎖) | SELECT * FROM t WHERE a='1' FOR UPDATE NOWAIT; | 立即報錯 ORA-00054 |
| (持有鎖) | SELECT * FROM t WHERE a='1' FOR UPDATE WAIT 6; | 等待 6 秒後報錯 ORA-30006 |
| (持有鎖) | SELECT * FROM t WHERE a='1' FOR UPDATE NOWAIT SKIP LOCKED; | 直接返回空結果,不等待、不報錯 |
SELECT o.order_id, o.amount
FROM orders o, order_items i
WHERE o.order_id = i.order_id
AND o.status = 'NEW'
FOR UPDATE OF o.status; -- 僅鎖定 orders 表的狀態欄,order_items 不加鎖
若省略 OF,兩表符合條件的所有行均會被鎖定,降低併發吞吐。
SKIP LOCKED 實現高併發任務佇列消費模型。SELECT ... FOR UPDATE 是 Oracle 解決「查詢後更新」併發問題的利器。掌握 WAIT/NOWAIT/SKIP LOCKED 及 OF 子句的組合運用,能精準控制鎖行為,構建高可靠、高併發的業務系統。