Oracle 多版本並發控制(MVCC)引入一致性讀與當前模式讀兩種存取模式。UPDATE 語句同時使用兩者:WHERE 子句用一致性讀過濾、SET 子句用當前模式讀修改。兩者間的時間差可能導致邏輯異常。本文透過兩個精心設計的並發案例深度剖析。
| 讀取模式 | SCN 基準 | 用途 | 特性 |
|---|---|---|---|
| 一致性讀 | 查詢啟動時的 SCN(Statement-level)或交易啟動 SCN(Transaction-level) | SELECT、WHERE 子句過濾 | 唯讀、快照隔離、不阻塞寫入 |
| 當前模式讀 | 當前最新提交 SCN | UPDATE/DELETE/MERGE 的 SET 子句、鎖定列 | 可寫、需取得鎖、看到最新提交資料 |
-- 初始狀態:t_test1 有一列 object_id=-1, secondary='A'
-- Session 1 (10:22:25) 啟動 UPDATE,含大型子查詢延遲 50 秒
UPDATE t_test1
SET secondary = 'B'
WHERE object_id = -1
AND secondary = 'B'
AND (SELECT COUNT(*) FROM t_test2 t1, t_test2 t2) > 0;
-- Session 2 (10:22:37) 更新同一列並提交
UPDATE t_test1 SET secondary='B' WHERE object_id=-1;
COMMIT; -- 10:23:02
-- Session 1 子查詢結束,進入當前模式讀階段
-- 一致性讀看到 secondary='A'(Session 2 提交前狀態)
-- WHERE secondary='B' 不成立 → 0 rows updated
關鍵時序:
secondary='A'。secondary='B' 失效,無列進入當前模式讀修改階段。-- 初始狀態:secondary='A'
-- Session 1 (10:26:05) 啟動 UPDATE,WHERE secondary='A'
UPDATE t_test1
SET secondary = 'B'
WHERE object_id = -1
AND secondary = 'A'
AND (SELECT COUNT(*) FROM t_test2 t1, t_test2 t2) > 0;
-- Session 2 (10:26:16) 更新同一列為 'B' 並提交
UPDATE t_test1 SET secondary='B' WHERE object_id=-1;
COMMIT; -- 10:26:41
-- Session 1 一致性讀看到 secondary='A'(滿足 WHERE)
-- 當前模式讀嘗試修改,但該列已被 Session 2 鎖定/修改
-- 結果:0 rows updated(或等待鎖釋放後發現值已變)
UPDATE 執行階段:
WHERE 條件掃描索引/表,產生候選列集合(快照)。WHERE 條件仍成立,執行 SET 修改。若階段一到階段二之間,其他交易修改並提交了相同列,階段二會看到最新值,可能導致:
WHERE 的列不再滿足 → 略過修改(案例二)。WHERE 的列變為滿足 → 但已錯過階段一掃描,不會被修改(案例一反面)。| 場景 | 風險 | 緩解措施 |
|---|---|---|
| 長時間子查詢/函數在 WHERE 中 | 一致性讀快照過舊,大量列被錯過 | 將子查詢物化為暫存表/ WITH 子句,縮短階段一時間 |
| 高併發熱點列更新 | 當前模式讀發現值已變,邏輯失效 | 樂觀鎖:WHERE id=? AND version=? + SET version=version+1 |
| 批次更新大量列 | 階段一掃描久,階段二衝突率高 | 分批次 WHERE ROWNUM <= 1000 循環提交 |
| 需嚴格序列化 | 業務邏輯要求「讀到即鎖定」 | SELECT ... FOR UPDATE 先鎖再改,或 SERIALIZABLE 隔離級 |