SQL Server 提供了多個事務隔離級別,用于控制并發事務如何訪問和修改數據時的可見性、鎖定行為以及可能遇到的并發問題(如臟讀、不可重復讀、幻讀)。這些級別在數據一致性、并發性能和鎖定開銷之間進行權衡。
以下是 SQL Server 支持的主要隔離級別,分為 標準 ANSI 隔離級別 和 SQL Server 特有的擴展隔離級別:
📌 一、標準 ANSI 隔離級別
-
READ UNCOMMITTED
(讀未提交)- 行為:允許事務讀取其他事務尚未提交(可能被回滾)的數據(臟讀)。
- 鎖定:SELECT 語句不加共享鎖,因此不會阻塞其他事務的寫操作(更新/刪除),但可能被寫操作阻塞。
- 問題:可能發生臟讀、不可重復讀、幻讀。
- 適用場景:對數據準確性要求極低,追求最高并發性能且可容忍臟數據的場景(如近似統計)。
- 語法:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
或表提示WITH (NOLOCK)
😅。
-
READ COMMITTED
(讀已提交) - SQL Server 默認級別- 行為:確保事務只能讀取已提交的數據(避免臟讀)。
- 鎖定:SELECT 語句在讀取數據時加共享鎖,讀取完成后立即釋放鎖(即使事務未結束),不會阻塞其他事務的讀,但會阻塞其他事務對相同數據的寫(反之亦然)。
- 問題:可能發生不可重復讀(同一事務內兩次讀取同一行可能不同)、幻讀(同一查詢兩次執行返回的行集不同)。
- 變體:SQL Server 支持兩種實現:
READ COMMITTED
(基于鎖 - Locking):傳統方式,使用共享鎖。READ COMMITTED SNAPSHOT
(基于行版本 - RCSI):見下文擴展級別。
- 語法:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
。
-
REPEATABLE READ
(可重復讀)- 行為:確保同一事務內多次讀取相同行數據的結果一致(避免臟讀和不可重復讀)。
- 鎖定:SELECT 語句在讀取的數據上加共享鎖,并持有到事務結束(而不是讀完就釋放)。阻止其他事務修改這些行。
- 問題:可能發生幻讀(其他事務可以插入新行,導致同一查詢返回更多行)。
- 語法:
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
。
-
SERIALIZABLE
(可序列化)- 行為:最高隔離級別,確保事務完全串行執行的效果(避免臟讀、不可重復讀和幻讀)。
- 鎖定:SELECT 語句在查詢涉及的數據范圍(不僅僅是行)上加范圍鎖(Range Locks),并持有到事務結束。阻止其他事務修改已有數據,也阻止插入或刪除影響查詢結果的新數據。
- 問題:鎖定范圍最大,并發性能最低,死鎖風險最高。
- 語法:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
。
二、SQL Server 特有的擴展隔離級別 (基于行版本控制)
這些級別利用 tempdb
數據庫存儲數據的行版本,讀操作不阻塞寫操作,寫操作也不阻塞讀操作(非阻塞讀取),大大提高了并發性。
-
SNAPSHOT
(快照)- 行為:事務啟動時看到的是一個一致的數據庫快照(基于事務開始時間點)。在整個事務過程中,所有讀取操作都基于該快照,不受其他事務修改的影響。
- 鎖定:SELECT 不加共享鎖(讀取的是版本)。寫操作(UPDATE/DELETE)仍需獲取鎖并可能被阻塞/阻塞其他寫操作。
- 優點:避免臟讀、不可重復讀、幻讀(因為基于快照)。讀寫不互相阻塞(高并發)。
- 問題:
- 更新沖突:如果事務嘗試修改一個自其快照后被其他事務修改過的行,會收到
Update conflict
錯誤 (錯誤 3960),事務會中止(需要應用程序重試)。 tempdb
開銷:需要額外的空間和 I/O 來存儲行版本。
- 更新沖突:如果事務嘗試修改一個自其快照后被其他事務修改過的行,會收到
- 啟用要求:數據庫選項
ALLOW_SNAPSHOT_ISOLATION
必須設為ON
。 - 語法:
SET TRANSACTION ISOLATION LEVEL SNAPSHOT;
。
-
READ COMMITTED SNAPSHOT
(已提交讀快照 - RCSI)- 本質:這是
READ COMMITTED
隔離級別的行版本控制實現變體,不是一個獨立的 ANSI 級別。 - 行為:每個
SELECT
語句看到的是該語句開始時(不是事務開始時)已提交的所有數據。避免了臟讀。 - 鎖定:SELECT 不加共享鎖(讀取的是最新已提交的版本)。寫操作仍需鎖。
- 優點:避免了臟讀,讀操作不阻塞寫操作,寫操作也不阻塞讀操作。比
SNAPSHOT
更少的tempdb
版本存儲開銷(版本在語句結束時可能被清理)。 - 問題:仍然可能發生不可重復讀和幻讀(因為每個語句看到的是當前時間點的最新提交版本)。
- 啟用要求:數據庫選項
READ_COMMITTED_SNAPSHOT
必須設為ON
。開啟后,所有使用默認READ COMMITTED
級別的事務自動使用 RCSI 語義。 - 語法:開啟選項后,使用
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
即生效(無需特殊語法指定 RCSI)。
- 本質:這是
📊 隔離級別總結表
隔離級別 | 臟讀(Dirty Read) | 不可重復讀(Non-Repeatable Read) | 幻讀(Phantom Read) | 并發性 | 鎖定/阻塞行為 | 實現機制 |
---|---|---|---|---|---|---|
READ UNCOMMITTED | ? 可能 | ? 可能 | ? 可能 | ???? 最高 | 無共享鎖,讀寫可能互相阻塞 | 鎖(Locking) |
READ COMMITTED (默認 - 鎖) | ? 避免 | ? 可能 | ? 可能 | ??? | 共享鎖(即時釋放),讀寫互斥 | 鎖(Locking) |
REPEATABLE READ | ? 避免 | ? 避免 | ? 可能 | ?? | 共享鎖(事務結束釋放) | 鎖(Locking) |
SERIALIZABLE | ? 避免 | ? 避免 | ? 避免 | ? 最低 | 范圍鎖(事務結束釋放) | 鎖(Locking) |
SNAPSHOT | ? 避免 | ? 避免 | ? 避免 | ??? | SELECT無鎖(讀版本) | 行版本控制(RVC) |
READ COMMITTED SNAPSHOT (RCSI) | ? 避免 | ? 可能 | ? 可能 | ???? | SELECT無鎖(讀版本) | 行版本控制(RVC) |
🔧 選擇建議
- 默認 (
READ COMMITTED
) / RCSI:適用于大多數場景,平衡一致性和性能。RCSI 是許多現代應用的首選,因其讀寫不阻塞的特性。 SNAPSHOT
:需要事務內讀取完全一致且避免所有 ANSI 異常,并能處理更新沖突的場景。REPEATABLE READ
/SERIALIZABLE
:需要嚴格保證可重復讀或避免幻讀,且能接受較高鎖開銷和死鎖風險的場景。READ UNCOMMITTED
:僅用于對臟讀不敏感、追求極致讀取速度的場景(慎用)。
💡 關鍵點
- 設置級別:
SET TRANSACTION ISOLATION LEVEL <level>;
(會話級) 或使用表提示 (如WITH (NOLOCK)
,WITH (SNAPSHOT)
)。 READ COMMITTED SNAPSHOT
和SNAPSHOT
需要先在數據庫級別啟用相應選項 (ALTER DATABASE ... SET ... ON
)。- 隔離級別主要影響
SELECT
語句的行為和鎖定,寫操作 (INSERT
,UPDATE
,DELETE
,MERGE
) 在任何級別下通常仍需要獲取并持有適當的鎖(如排他鎖)。 - 選擇合適的隔離級別對應用程序的正確性、性能和可伸縮性至關重要。