首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 數據庫知識 >  >> Oracle 資料庫 >> Oracle 資料庫基礎

Oracle 當前模式讀與一致性讀續篇:並發 UPDATE 邏輯衝突導致資料不一致實證

     編輯:Oracle 資料庫基礎

承接前文,本文展示更隱蔽的邏輯衝突:兩條語義等價的 UPDATE,因一致性讀快照與當前模式讀的時間差,在並發下產生「邏輯上應相同的列卻寫入不同值」的資料不一致現象。

測試場景

-- 目標:讓 object_id=101 和 102 的 lio 值永遠相同
-- 方法 1:單一 UPDATE 設定常數
UPDATE t_test1 SET lio=0 WHERE object_id IN (101,102);

-- 方法 2:子查詢複製 101 的值給 102(含延遲子查詢)
UPDATE t_test1
SET    lio = (SELECT lio FROM t_test1 WHERE object_id=101)
WHERE  object_id = 102
AND    (SELECT COUNT(*) FROM t_test2 t1, t_test2 t2) > 0;

並發實驗過程

  1. 初始化:UPDATE t_test1 SET lio=1 WHERE object_id IN (101,102); COMMIT; → 兩列 lio=1。
  2. Session 1 (13:29:11) 執行方法 2(含大型子查詢延遲)。
  3. Session 2 (13:29:16) 執行方法 1(SET lio=0)並提交。
  4. Session 1 子查詢結束,進入當前模式讀階段。

結果分析

SELECT object_id, lio FROM t_test1 WHERE object_id IN (101,102);
-- 結果:
OBJECT_ID  LIO
---------  ---
101        0   -- 被 Session 2 更新為 0
102        1   -- 被 Session 1 更新為 1(來自一致性讀快照中的 101 值)

根因還原

  1. Session 1 啟動,SCN=100。一致性讀掃描 WHERE object_id=102,同時執行子查詢 SELECT lio FROM t_test1 WHERE object_id=101 → 讀到 lio=1(SCN=100 快照)。
  2. Session 2 執行 UPDATE ... SET lio=0 WHERE object_id IN (101,102),SCN=105 提交。兩列在當前模式下均變為 0。
  3. Session 1 進入當前模式讀,對 object_id=102 取得鎖,重新驗證 WHERE 條件(仍成立),執行 SET lio = 1(使用階段一讀到的快照值 1)。
  4. 最終:101=0(Session 2)、102=1(Session 1 快照值)。邏輯不變性被破壞。

這是 MVCC 的缺陷嗎?

非缺陷,而是設計權衡:MVCC 犧牲「讀寫一致性」換取「讀不阻塞寫、寫不阻塞讀」的高併發吞吐。Oracle 文件明確說明:UPDATE 的 SET 子句使用查詢啟動時的一致性快照值,而非當前值。

業務層面的解決方案

方案實作優點缺點
樂觀鎖版本欄ADD version NUMBER; UPDATE ... SET lio=..., version=version+1 WHERE id=? AND version=?應用層可感知衝突、重試需修改表結構、應用碼
SELECT FOR UPDATE 先鎖SELECT lio FROM t_test1 WHERE object_id=101 FOR UPDATE; 再 UPDATE序列化存取、強一致性鎖競爭、降低併發
SERIALIZABLE 隔離級SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;語句級可序列化ORA-08177 錯誤頻發、需重試邏輯
單一 UPDATE 避免子查詢UPDATE t_test1 SET lio=0 WHERE object_id IN (101,102);原子性、無快照問題不適用「複製其他列值」場景
物化子查詢結果WITH src AS (SELECT lio FROM t_test1 WHERE object_id=101) UPDATE ... SET lio=(SELECT lio FROM src) ...縮短一致性讀窗口仍有微小時間差

最佳實務建議

  1. 避免在 UPDATE SET 子句使用子查詢讀取同表其他列,改用應用層讀取後帶入常數值。
  2. 若必須複製值,使用 MERGE 或 SELECT ... FOR UPDATE 明確序列化。
  3. 關鍵業務不變性(如「兩列值必相同」),在資料庫層加 CHECK 約束或觸發器驗證,或應用層雙寫驗證。
  4. 監控 V$SEGMENT_STATISTICS 中 logical reads 暴增的物件,往往暗示長查詢導致一致性讀成本高。

深入理解一致性讀快照「凍結」SET 子句值的機制,能從架構層面預防並發更新邏輯衝突,打造在高併發下依然邏輯自洽的核心交易系統。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved