man

他的聲音低沉,又總是板著一張臉,走在路上連大人都會不自覺讓開,更別說孩子了。小孩一靠近他,他眉頭一皺,小朋友就嚇得跑掉。

久而久之,村裡流傳的故事越變越誇張,有人說他脾氣壞、有人說他討厭小孩、有人說他從不關心別人
這些話像一堆未經查證的資訊在村裡亂流,就像大家用著 ReadUncommitted 的方式在認識他,還沒看清就急著下結論。

一個人的真心,不是聽來的,而是他在困境裡做出的選擇,謠言說的都是 Uncommitted 的版本,唯有親眼看見的 Snapshot,才是真實的他

在同一段交易裡,你接受看到「哪一種樣子的世界」?

在多人同時查資料、改資料的世界裡,不可能做到「既快、又完全正確、又不互相干擾」。
資料庫永遠在做取捨,而 IsolationLevel 本質上就在回答這個問題

你想優先保護什麼?速度?正確性?穩定性?還是資料不被干擾?

你想要什麼? 你必須接受什麼代價? 適合的 IsolationLevel 評價
⚡ 最快速度 ❌ 資料可能不準 ReadUncommitted 那些流言蜚語都聽進去
✔ 正確資料 ❌ 等別人寫完才能讀 ReadCommitted 很乖但慢一點可以看
✔ 查完後資料不能變 ❌ 鎖多、會卡住 RepeatableRead 只鎖住在意的部分,個人主義
✔ 查詢期間世界一致 ❌ 舊版本、版本庫大 Snapshot 別人偷改資料前叫他揪都馬得我要先拷貝一份舊版
✔ 整個世界不准動,等我做完 ❌ 最慢、鎖最多 Serializable 被罵有特權

g

f

op

     什麼都無法捨棄的人,就什麼都無法改變 - Armin Arlert


Armin Arlert

Snapshot 的核心是 『我不保證你讀的是最新,但一定保證你讀的是真實存在的資料』

「在你查資料那一刻世界的樣子,我把正確的資料凍結給你。」

就像按下快門那一瞬間,你看到的畫面,就是接下來整段查詢要依據的真實,哪怕外界已經天翻地覆,哪怕別的交易把資料改了又改,你看到的世界都不會被扭曲或破碎


    你只要按下按鈕,其餘的都交給我們


     - 柯達創始人喬治·伊士曼(George Eastman)於 1888 年創造的廣告語

kfj

我們現在已經知道,在查詢時,使用 SnapShot 的隔離層級查詢資料,資料庫會適時地幫我們記住資料,也就是快照,也就是說,我們需要一個「記住世界的時間點」作為基準,也就是 Timestamp


查詢開始前 - 建立 Snapshot Timestamp(版本時間點)

✅ SP 第一次查資料時,SQL Server 會建立一個「Snapshot Timestamp」,稱為 Transaction Version。

這個時間點就像拍照那一瞬間,接下來你看到的資料都只能來自「這一刻之前」的版本

fk

資料異動被異動!

✅ 如果其他交易在這個 snapshot timestamp 之後對某筆資料做了 UPDATE 或 DELETE
🔁 SQL Server 會自動把「修改前的版本」備份到 tempdb 的 version store

tips : 不是你查詢就會備份,是 你正在查 + 有人動資料 才會備份

f

拿資料的當下:依據快照時間點決定資料來源

狀況(資料相對於 Snapshot Timestamp 的狀態) SQL Server 判斷 資料來源 查詢結果 為什麼會這樣?(本質)
1️⃣ 資料在快照時間點之前就存在,而且沒被改過 ✔ 版本符合快照 主資料表 ✔ 看得到 它本來就存在於你當時「拍下的世界」裡
2️⃣ 資料在快照時間點之前存在,但之後被修改過 ❌ 主表太新 → 不能讀主表 tempdb version store ✔ 看得到(舊版本) 你不能看到快照後的變化,所以改用舊版本
3️⃣ 資料在快照後才被新增(INSERT) ❌ 不符合快照 ✖ 看不到 因為新增的資料不在你的照片裡
4️⃣ 資料在快照後被刪除(DELETE) ❌ 主表沒有該資料 tempdb version store 有舊版本 ✔ 看得到(舊版本) 在你拍照時它還存在,所以要還給你
5️⃣ 資料在快照時間點後被更新,但更新前沒有舊版本存下來(極少見,通常因沒有 Snapshot 查詢在場) ❌ 沒有可用版本 ✖ 看不到 / 或錯誤 沒有 Snapshot 查詢,就不會備份舊版本給你
6️⃣ 沒有任何修改行為(表很安靜) ✔ 所有資料版本都穩定 主資料表 ✔ 全部可讀 沒有版本衝突 → Snapshot 走得最順的情況

以上就是使用這個隔離層級去查詢資料,整個過程發生了甚麼事,他與 ReadUnCommitted 相同不會與其他交易互卡,但可以確保資料不會是破碎的髒資料

f

要讓 Snapshot 正常運作,其實不是只在程式裡 SET TRANSACTION ISOLATION LEVEL SNAPSHOT 就好,因為 Isolation Level 只是「查詢提出的要求」,資料庫要先「準備好版本機制」,才能滿足這個要求

SQL Server 要能提供 Snapshot,就必須先有能力「幫你留住舊版本」。這件事是由兩個資料庫層級的設定決定的

  • ALLOW_SNAPSHOT_ISOLATION
  • READ_COMMITTED_SNAPSHOT

只有當資料庫有準備好相機時,SQL Server 才會啟動 Row Versioning(行版本控制) 機制,也才會在 tempdb 裡幫你保存「被修改前的舊版本資料」

f

SQL Server 不能只留現在的資料,還得額外多記一份「過去版本」,也就是 Row Version(行版本)。而這些「舊版本」是放在 tempdb 的 version store 裡

使用 SNAPSHOT 的本質是「資料庫要額外幫你記住查資料時刻的樣子(也就是所謂的版本)」,這麼做雖然讓查詢的資料變得穩定且一致

  • 資料庫要維護更多的資料版本(Row Version)
  • 資料庫的 記憶體、磁碟、效能開銷都會變大
  • 同時,也會有某些「寫入的衝突問題」

每一次資料更新時,SQL Server 不能直接改資料,而是要把「原本的資料」複製一份到 tempdb、再去修改主資料表中的值,也就是說,你一筆 UPDATE,實際上產生了兩份資料!

  • TempDB 負擔變重
  • 硬碟 I/O 增加
  • 若 tempdb 空間不足,會導致查詢失敗(錯誤訊息:version store full)
  • 頻繁寫入下,效能下降嚴重

vv

雖然 SNAPSHOT 查資料時不會被鎖,但「寫資料」的時候會做版本比對,發現你讀的版本跟當前版本不一致時,就會直接拋出錯誤 : Snapshot isolation transaction aborted due to update conflict,結果可能造成造成交易失敗

v

Generally, 查多、寫少 的系統會比較適合,可以避免 Dirty Read 被放大

  • 報表、BI
  • API(查資料居多)
  • 電商 Checkout
  • POS
  • 股價、報價系統

因為 CRUD 可能太頻繁了,導致要存一堆版本 → tempdb 可能會爆掉,遇到 Update Conflict 更高也會讓交易一直失敗

系統透過 API 取得線上訂單,訂單資訊會被分成兩個步驟查詢

  • getlist:查詢訂單清單
  • get:查詢單筆訂單詳細內容

然而,在高 RPS 的環境下,因為使用了 WITH (NOLOCK)(也就是 ReadUncommitted ),導致系統讀到「不一致的資料」,進一步影響了後續出貨工作

  1. 2025-08-06 09:08:00 (UTC+8) 查詢訂單 getlist 回傳了 TG250806K00007 | TM250806K00007 有 2 筆 TS (TS250806K000018 及 TS250806K000019)
  2. 2025-08-06 09:08:01 (UTC+8) 取得訂單詳情 get 時 “TM250806K00007”,API 只回傳 1個 TS250806K000019,缺少了 TS250806K000018 ,導致未能完成出貨流程!

v

查案後發現,針對 csp_GetSalesOrderDataForApi 中,為避免髒讀 (Dirty Read) 與幻讀 (Phantom Data),建議將隔離層級改為 SNAPSHOT 並移除 WITH (NOLOCK)

v

一天,一場突如其來的大雨打亂了村子。有個孩子跌倒、哭著回不了家,大家都在忙著躲雨時,只有他撐著一把又舊又大的傘,默然地走向小孩,把傘蓋到孩子頭上,自己則濕了一片。那一刻,所有人都愣住了

那皺眉背後是擔心;那沉默只是不擅言語

村裡的此時意識到隔離層級沒有做適當的設計,過去總是聽來的、揣測的、未經證實的版本,不如透過自己親眼看到的 Snapshot

g

f

k