一、事務隔離級別控制着事務的如下表現:
- 讀取數據時是否占用鎖以及所請求的鎖類型。
- 占用讀取鎖的時間。
- 引用其他事務修改的行的讀操作是否:
-
- 在該行上的排他鎖被釋放之前阻塞其他事務。
- 檢索在啟動語句或事務時存在的行的已提交版本。
- 讀取未提交的數據修改。
以上說明事務隔離級別主要針對讀操作來說的。(DML語句我們可以不考慮事務隔離級別,因為任何事物隔離級別下DML的加鎖都很嚴格,屬於得不到就等待的類型)
二、臟讀、不可重復讀、幻讀的區別:
提到事物隔離級別就不能不提這3個概念,可以說事務隔離級別就是為了避免這3種情況出現的。
臟讀:讀到了其他事務已修改但未提交的數據
不可重復讀:由於其他事務的修改,導致同一事務中兩次查詢讀到的數據不同
幻讀:由於其他事務的修改,導致同一事務中兩次查詢讀到的記錄數不同
可能有人對幻讀和不可重復讀的定義不太理解,兩者最大的區別實質上在於加鎖的不同,后邊會有講解。
三、ANSI/ISO標准定義了下列事務隔離級別,SQL Server數據庫引擎支持全部這4種隔離級別:
因此四種隔離級別與臟讀、幻讀、不可重復讀的對應情況如下:

需要特別提醒的是:雖然Mysql、Oracle所支持的事務隔離級別也基本遵循ANSI標准,但卻有很大區別:
- Oracle只支持已提交讀和序列化讀。
-
Mysql默認的的可重復讀隔離級別通過范圍鎖實現了避免幻讀。
四、除以上4種隔離級別外SQL Server還支持使用行版本控制的其他兩個事務隔離級別:
-
一個是默認的read committed隔離級別下的snapshot實現,嚴格來說並不算一個事務隔離級別,只是read committed的一個特殊形態。
-
一個是全新的事務隔離級別----快照隔離級別。
兩者的開啟方式為:
1、如果要開啟SNAPSHOT事務隔離級別,需要預先設置ALLOW_SNAPSHOT_ISOLATION為ON,且目前只能修改會話級別的事務隔離級別。
ALTER DATABASE [dbname] SET ALLOW_SNAPSHOT_ISOLATION ON; --需要單用戶模式下修改,因為要加庫級別的獨占鎖。
然后執行如下語句修改事務隔離級別:(修改后只在會話級別生效,無法修改全局級別的事務隔離級別)
SET TRANSACTION ISOLATION LEVEL { READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SNAPSHOT | SERIALIZABLE }
2、使用READ_COMMITTED_SNAPSHOT,則直接執行下列ALTER語句修改,是在默認的READ COMMITTED隔離級別下修改的,此隔離級別修改后永久生效,使用dbcc useroptions查看可以看到事務隔離級別被全局的修改成了read committed snapshot。
ALTER DATABASE [dbname] SET READ_COMMITTED_SNAPSHOT ON;
兩者的區別在於:
READ_COMMITTED_SNAPSHOT是指Select語句總是讀取最新的已提交的數據,即如果有DML事務正在執行,那么select語句不會被阻塞而是讀取這些DML事務預先生成的前鏡像,這種讀只會在表上加Sch-S鎖,其他的行鎖頁鎖全部沒有。DML數據一旦提交,再次執行Select語句就會立馬讀到新的數據。
SNAPSHOT隔離級別與上述的區別在於,如果你在同一個事務內執行兩次相同的select語句,那么即便在這兩次select語句之間發生了數據更改且提交,兩次讀到的數據也是一樣的。
用官網的一句話來描述兩者區別就是:READ_COMMITTED_SNAPSHOT提供語句級的一致性,SNAPSHOT事務隔離級別提供事務級的一致性。
五、全部6種隔離級別的加鎖模式:
開始說過事務隔離級別主要就是控制讀操作加什么鎖,鎖占用多長時間的的,因此只有搞清各事務隔離級別下的加鎖機制才能徹底搞清事務隔離級別的概念和他們的不同。
1.未提交讀
2.已提交讀
select語句對讀取的數據正常加鎖,但是不等事務結束才釋放鎖,而是讀完一個頁就會釋放,會出現不可重復讀和幻讀。DML語句正常加鎖。
3.已提交讀快照
SQL Server特有的隔離級別,主要是為了匹配Oracle的已提交讀實現的功能,在此隔離級別下,select只會對表加一個Sch-S鎖,因此select不會引發在阻塞,但是會加大tempdb的使用量。DML正常加鎖。
4.快照
同上,select也只加Sch-S鎖,唯一區別在於實現的一致性讀是事務級別的,即快照在tempdb中保留的時間更長。DML正常加鎖。
這里猜測快照讀隔離級別下會在事務第一次select時在tempdb中建立一個完整的快照,而不是僅依賴於MVCC的行版本機制。
5.可重復讀
可重復讀加的鎖與已提交讀完全一致,區別在於只有在整個事務完成后才會釋放鎖,而不是讀完一個頁就釋放,此種加鎖方式也避免了不可重復讀,因為事務期間其他DML無法獲取資源上的鎖。
此隔離級別下可以避免不可重復讀,但是不可避免幻讀。DML正常加鎖。
6.序列化讀
序列化讀加的鎖與已提交讀有區別,此隔離級別下select操作對索引鍵加的是鍵范圍鎖,而不是普通的S、U、X、IS、IU、IX等。
鍵范圍鎖的機制基本與Mysql中的范圍鎖相似,主要是為了防止幻讀,其機制在於select操作不但會將讀到的鍵值鎖定,還會將上下鍵值的范圍也鎖定。
舉例如下:
有主鍵為1,5,8,9,10的記錄,select ... where col between 3 and 7;會使用鍵范圍鎖將5這條記錄鎖定,除此之外還會用一個鍵范圍鎖將346這幾個虛幻的記錄也鎖定,這樣就不能在讀取操作期間插入數據了,可以防止幻讀。
Ps:對於序列化加的鍵范圍鎖是否是我上邊所說的那么精確,還需要具體實驗,這里只是根據官網猜測會使用多余的一個鍵范圍鎖鎖定可能造成幻讀的記錄(總的鍵范圍鎖數目為n+1個,n為滿足查詢條件的行數),具體實驗方法參見我的另一篇博客,有興趣的可以試試。
http://www.cnblogs.com/leohahah/p/7059852.html
總結:
可以看到SQL Server通過MVCC多版本控制機制在3、4兩種隔離級別下實現select語句的不加鎖讀取,避免了阻塞。在已提交讀快照隔離級別下通過mvcc實現了select不阻塞,但是依然會有不可重復讀和幻讀現象。在快照隔離級別下通過mvcc實現了select不阻塞,同時由於是事務級的快照,也順帶避免了不可重復讀和幻讀。因此可以發現幻讀的解決方式目前只有兩種:1是鍵范圍鎖,2是mvcc機制的事務級鏡像(即snapshot隔離級別的方式)。
Ps:關於Mvcc機制的實現方式參考https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/sql/snapshot-isolation-in-sql-server,但是此文中關於snapshot和read_committed_snapshot的解釋有些矛盾,文中把這兩種隔離級別混淆了,但是事實上通過試驗可以看到這兩種isolation level的差別與Mysql中RR和RC下一致性讀的差別是很相似的。正如我之前所寫的snapshot實現的是事務級的一致性,而read_committed_snapshot實現的是語句級的一致性。
參考文檔:
https://docs.microsoft.com/zh-cn/sql/t-sql/statements/set-transaction-isolation-level-transact-sql
https://msdn.microsoft.com/zh-cn/library/jj856598(v=sql.120).aspx