飛機訂票等高併發場景,需確保座位數檢查與扣減的原子性,避免超賣。
PROCEDURE book_ticket(...) IS
v_cnt NUMBER;
v_res VARCHAR2(50);
BEGIN
-- 1. 共享鎖:允許並發讀,阻擋 DDL/全表獨佔鎖
LOCK TABLE tb_book_ticket IN SHARE MODE;
-- 2. 先插入訂單 (佔座)
INSERT INTO tb_book_ticket (id, scheduled_flight, ticket_no, book_time)
VALUES (seq_id.NEXTVAL, v_flight, v_ticket, SYSDATE);
-- 3. 再統計該航班已訂數量
SELECT COUNT(*) INTO v_cnt
FROM tb_book_ticket
WHERE scheduled_flight = v_flight;
-- 4. 判斷是否超額
IF v_cnt <= v_max_seats THEN
v_res := 'book successfully';
COMMIT;
ELSE
v_res := 'book unsuccessfully';
ROLLBACK; -- 釋放鎖並回滾插入
END IF;
RETURN v_res;
END;
| 要點 | 錯誤做法 | 正確做法 |
|---|---|---|
| 鎖模式 | EXCLUSIVE MODE (串行化,併發極低) | SHARE MODE (並發讀,僅阻擋 DDL/獨佔) |
| 檢查順序 | 先 COUNT 再 INSERT | 先 INSERT 再 COUNT |
若先 COUNT,User A 讀到 99/100,User B 也讀到 99/100,兩者皆通過檢查並 INSERT → 變 101/100 超賣。
先 INSERT 讓該筆記錄對自身可見 (未提交也可見),COUNT 會包含自己剛插入的那筆,配合 SHARE MODE 序列化關鍵區段,確保準確。
SELECT ... FOR UPDATE SKIP LOCKED (11g+)