在READ UNCOMMITTED事務隔離級別下或使用WITH(NOLOCK)來查詢數據時,會出現臟讀情況,因此對於一些比較"關鍵"的業務,會要求不能使用WITH(NOLOCK)或允許在READ UNCOMMITTED事務隔離級別下,於是我們使用默認的READ COMMITTED隔離級別來訪問數據,但是這樣真的就沒有問題么?
讓我們來做個小實驗
准備測試數據
--======================================= --創建測試表 CREATE TABLE TB106 ( C0 INT IDENTITY(1,1) PRIMARY KEY, C1 INT, C2 CHAR(100), C3 NVARCHAR(4000) ) GO --======================================= --創建一個非聚簇索引 CREATE INDEX IX_C1_C2 ON TB106(C1,C2) GO --======================================= --向表中填充1000條數據 DECLARE @ID INT SET @ID=0 WHILE(@ID<1000) BEGIN INSERT INTO TB106(C1,C2,C3) SELECT @ID,@ID, REPLICATE('A',3800) SET @ID=@ID+1 END GO --================================= --查看表中數據,共1000行 SELECT * FROM TB106
開啟回話1,運行以下腳本
--====================== --開啟事務,更新C0為100的數據 BEGIN TRAN UPDATE TB106 SET C1=101
WHERE C0=100
開啟回話2,運行以下腳本
--======================= --查詢數據 SELECT C1,C2,C0 FROM TB106
我們會發現回話2被回話1阻塞,但是已經有少量數據開始被讀取
我們再次回到回話1,繼續執行以下腳本
--===================== --更新C0為5的數據,並提交事務 UPDATE TB106 SET C1=1000 WHERE C0=5 COMMIT
伴隨着回話1事務的提交,回話2沒有了阻塞,順利完成查詢,但是奇跡出現了
表中只有1000行數據,為什么我們能查出1001行數據來呢?
我們來分析下執行結果,不難發現c0=5的數據被讀取了兩遍,更新前后的數據都被讀取到,這不科學!在c0=5的數據被更新前,數據被讀取了一遍,然后當讀到c0=100的時候,回話被阻塞,然后c0=5的數據被更新,更新后的數據記錄存放位置變動,移到了索引尾部,當阻塞結束后,該記錄又再次被讀取,從而導致一行記錄被讀取兩遍。
--=====================================================================
這並不是MS的bug,讓我們來仔細閱讀下各種隔離級別的解釋:
READ UNCOMMITTED 指定語句可以讀取已由其他事務修改但尚未提交的行。 在 READ UNCOMMITTED 級別運行的事務,不會發出共享鎖來防止其他事務修改當前事務讀取的數據。READ UNCOMMITTED 事務也不會被排他鎖阻塞,排他鎖會禁止當前事務讀取其他事務已修改但尚未提交的行。設置此選項之后,可以讀取未提交的修改,這種讀取稱為臟讀。在事務結束之前,可以更改數據中的值,行也可以出現在數據集中或從數據集中消失。該選項的作用與在事務內所有 SELECT 語句中的所有表上設置 NOLOCK 相同。這是隔離級別中限制最少的級別。
READ COMMITTED 指定語句不能讀取已由其他事務修改但尚未提交的數據。這樣可以避免臟讀。其他事務可以在當前事務的各個語句之間更改數據,從而產生不可重復讀取和幻像數據。該選項是 SQL Server 的默認設置。
REPEATABLE READ 指定語句不能讀取已由其他事務修改但尚未提交的行,並且指定,其他任何事務都不能在當前事務完成之前修改由當前事務讀取的數據。 對事務中的每個語句所讀取的全部數據都設置了共享鎖,並且該共享鎖一直保持到事務完成為止。這樣可以防止其他事務修改當前事務讀取的任何行。其他事務可以插入與當前事務所發出語句的搜索條件相匹配的新行。如果當前事務隨后重試執行該語句,它會檢索新行,從而產生幻讀。由於共享鎖一直保持到事務結束,而不是在每個語句結束時釋放,所以並發級別低於默認的 READ COMMITTED 隔離級別。此選項只在必要時使用。
--=====================================================================
誤區:不知道有多少朋友和我一樣,錯誤認為REPEATABLE READ分離級別只是為了保證兩次SQL查詢的數據不發生變化,而忽略了在一次查詢期間內數據發生變化導致的問題。而由於導致該問題的發生概率比較低,往往不能引起我們足夠重視,從而錯誤地認為READ COMMITTED隔離級別可以勝任類似需求。
--======================================================================
依舊是妹子壓貼