SQL 事務隔離級別 概述 隔離級別用於決定如果控制併發用戶如何讀寫數據的操作,同時對性能也有一定的影響作用。 步驟 事務隔離級別通過影響讀操作來間接地影響寫操作;可以在回話級別上設置事務隔離級別也可以在查詢(表級別)級別上設置事務隔離級別。事務隔離級別總共有6個隔離級別:READ UNCOMMIT ...
SQL 事務隔離級別
概述
隔離級別用於決定如果控制併發用戶如何讀寫數據的操作,同時對性能也有一定的影響作用。
步驟
事務隔離級別通過影響讀操作來間接地影響寫操作;可以在回話級別上設置事務隔離級別也可以在查詢(表級別)級別上設置事務隔離級別。
事務隔離級別總共有6個隔離級別:
READ UNCOMMITTED(未提交讀,讀臟),相當於(NOLOCK)
READ COMMITTED(已提交讀,預設級別)
REPEATABLE READ(可以重覆讀),相當於(HOLDLOCK)
SERIALIZABLE(可序列化)
SNAPSHOT(快照)
READ COMMITTED SNAPSHOT(已經提交讀隔離)
對於前四個隔離級別:READ UNCOMMITTED<READ COMMITTED<REPEATABLE READ<SERIALIZABLE
隔離級別越高,讀操作的請求鎖定就越嚴格,鎖的持有時間久越長;所以隔離級別越高,一致性就越高,併發性就越低,同時性能也相對影響越大.
獲取事務隔離級別(isolation level)
DBCC USEROPTIONS
設置隔離
設置回話隔離 SET TRANSACTION ISOLATION LEVEL <ISOLATION NAME> --註意:在設置回話隔離時(REPEATABLE READ)兩個單詞需要用空格間隔開,但是在表隔離中可以粘在一起(REPEATABLEREAD) 設置查詢表隔離 SELECT ....FROM <TABLE> WITH (<ISOLATION NAME>)
1.READ UNCOMMITTED
READ UNCOMMITTED:未提交讀,讀臟數據
預設的讀操作:需要請求共用鎖,允許其他事物讀鎖定的數據但不允許修改.
READ UNCOMMITTED:讀操作不申請鎖,運行讀取未提交的修改,也就是允許讀臟數據,讀操作不會影響寫操作請求排他鎖.
創建測試數據
IF OBJECT_ID('Orders','U') IS NOT NULL DROP TABLE Orders GO CREATE TABLE Orders (ID INT NOT NULL, Price FLOAT NOT NULL ); INSERT INTO Orders VALUES(10,10.00),(11,11.00),(12,12.00),(13,13.00),(14,14.00); GO SELECT ID,Price FROM Orders
新建回話1將訂單10的價格加1
BEGIN TRANSACTION UPDATE Orders SET Price=Price+1 WHERE ID=10 SELECT ID,Price FROM Orders WHERE ID=10
在另一個回話2中執行查詢操作
首先不添加隔離級別,預設是READ COMMITTED,由於數據之前的更新操作使用了排他鎖,所以查詢一直在等待鎖釋放*/ SELECT ID,Price FROM Orders WHERE ID=10 ---將查詢的隔離級別設置為READ UNCOMMITTED允許未提交讀,讀操作之前不請求共用鎖。 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED SELECT ID,Price FROM Orders WHERE ID=10; --當然也可以使用表隔離,效果是一樣的 SELECT ID,Price FROM Orders WITH (NOLOCK) WHERE ID=10
假設在回話1中對操作執行回滾操作,這樣價格還是之前的10,但是回話2中則讀取到的是回滾前的價格11,這樣就屬於一個讀臟操作
ROLLBACK TRANSACTION
2.READ COMMITTED
READ COMMITTED(已提交讀)是SQL SERVER預設的隔離級別,可以避免讀取未提交的數據,隔離級別比READ UNCOMMITTED未提交讀的級別更高;
該隔離級別讀操作之前首先申請並獲得共用鎖,允許其他讀操作讀取該鎖定的數據,但是寫操作必須等待鎖釋放,一般讀操作讀取完就會立刻釋放共用鎖。
新建回話1將訂單10的價格加1,此時回話1的排他鎖鎖住了訂單10的值
BEGIN TRANSACTION UPDATE Orders SET Price=Price+1 WHERE ID=10 SELECT ID,Price FROM Orders WHERE ID=10
在回話2中執行查詢,將隔離級別設置為READ COMMITTED
SET TRANSACTION ISOLATION LEVEL READ COMMITTED SELECT ID,Price FROM Orders WHERE ID=10 ---由於READ COMMITTED需要申請獲得共用鎖,而鎖與回話1的排他鎖衝突,回話被堵塞, ----在回話1中執行事務提交 COMMIT TRANSACTION /*由於回話1事務提交,釋放了訂單10的排他鎖,此時回話2申請共用鎖成功查到到訂單10的價格為修改後的價格11,READ COMMITTED由於是已提交讀隔離級別,所以不會讀臟數據. 但是由於READ COMMITTED讀操作一完成就立即釋放共用鎖,讀操作不會在一個事務過程中保持共用鎖,也就是說在一個事務的的兩個查詢過程之間有另一個回話對數據資源進行了更改,會導致一個事務的兩次查詢得到的結果不一致,這種現象稱之為不可重覆讀.*/
重置數據
UPDATE Orders SET Price=10 WHERE ID=10
3.REPEATABLE READ
REPEATABLE READ(可重覆讀):保證在一個事務中的兩個讀操作之間,其他的事務不能修改當前事務讀取的數據,該級別事務獲取數據前必須先獲得共用鎖同時獲得的共用鎖不立即釋放一直保持共用鎖至事務完成,所以此隔離級別查詢完並提交事務很重要。
在回話1中執行查詢訂單10,將回話級別設置為REPEATABLE READ
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ BEGIN TRANSACTION SELECT ID,Price FROM Orders WHERE ID=10
新建回話2修改訂單10的價格
UPDATE Orders SET Price=Price+1 WHERE ID=10 ---由於回話1的隔離級別REPEATABLE READ申請的共用鎖一直要保持到事務結束,所以回話2無法獲取排他鎖,處於等待狀態
在回話1中執行下麵語句,然後提交事務
SELECT ID,Price FROM Orders WHERE ID=10 COMMIT TRANSACTION
回話1的兩次查詢得到的結果一致,前面的兩個隔離級別無法得到一致的數據,此時事務已提交同時釋放共用鎖,回話2申請排他鎖成功,對行執行更新
REPEATABLE READ隔離級別保證一個事務中的兩次查詢到的結果一致,同時保證了丟失更新
丟失更新:兩個事務同時讀取了同一個值然後基於最初的值進行計算,接著再更新,就會導致兩個事務的更新相互覆蓋。
例如酒店訂房例子,兩個人同時預定同一酒店的房間,首先兩個人同時查詢到還有一間房間可以預定,然後兩個人同時提交預定操作,事務1執行number=1-0,同時事務2也執行number=1-0最後修改number=0,這就導致兩個人其中一個人的操作被另一個人所覆蓋,REPEATABLE READ隔離級別就能避免這種丟失更新的現象,當事務1查詢房間時事務就一直保持共用鎖直到事務提交,而不是像前面的幾個隔離級別查詢完就是否共用鎖,就能避免其他事務獲取排他鎖。
4.SERIALIZABLE
SERIALIZABLE(可序列化),對於前面的REPEATABLE READ能保證事務可重覆讀,但是事務只鎖定查詢第一次運行時獲取的數據資源(數據行),而不能鎖定查詢結果之外的行,就是原本不存在於數據表中的數據。因此在一個事務中當第一個查詢和第二個查詢過程之間,有其他事務執行插入操作且插入數據滿足第一次查詢讀取過濾的條件時,那麼在第二次查詢的結果中就會存在這些新插入的數據,使兩次查詢結果不一致,這種讀操作稱之為幻讀。
為了避免幻讀需要將隔離級別設置為SERIALIZABLE
IF OBJECT_ID('Orders','U') IS NOT NULL DROP TABLE Orders GO CREATE TABLE Orders (ID INT NOT NULL PRIMARY KEY, Price FLOAT NOT NULL, type INT NOT NULL ); INSERT INTO Orders VALUES(10,10.00,1),(11,11.00,1),(12,12.00,1),(13,13.00,1),(14,14.00,1); GO
在回話1中執行查詢操作,並將事務隔離級別設置為REPEATABLE READ(先測試一下前面更低級別的隔離)
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ BEGIN TRANSACTION SELECT ID,Price,type FROM Orders WHERE TYPE=1
在回話2中執行插入操作
INSERT INTO Orders VALUES(15,15.00,1)
返回回話1重新執行查詢操作並提交事務
SELECT ID,Price,type FROM Orders WHERE TYPE=1 COMMIT TRANSACTION
結果回話1中第二次查詢到的數據包含了回話2新插入的數據,兩次查詢結果不一致(驗證之前的隔離級別不能保證幻讀)
重新插入測試數據
IF OBJECT_ID('Orders','U') IS NOT NULL DROP TABLE Orders GO CREATE TABLE Orders (ID INT NOT NULL PRIMARY KEY, Price FLOAT NOT NULL, type INT NOT NULL ); INSERT INTO Orders VALUES(10,10.00,1),(11,11.00,1),(12,12.00,1),(13,13.00,1),(14,14.00,1); GO
接下來將回話級別設置為SERIALIZABLE,在回話1中執行查詢操作,並將事務隔離級別設置為SERIALIZABLE
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE BEGIN TRANSACTION SELECT ID,Price,type FROM Orders WHERE TYPE=1
在回話2中執行插入操作
INSERT INTO Orders VALUES(15,15.00,1)
返回回話1重新執行查詢操作並提交事務
SELECT ID,Price,type FROM Orders WHERE TYPE=1 COMMIT TRANSACTION
兩次執行的查詢結果相同
重置所有打開回話的預設隔離級別
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
5.SNAPSHOT
SNAPSHOT快照:SNAPSHOT和READ COMMITTED SNAPSHOT兩種隔離(可以把事務已經提交的行的上一版本保存在TEMPDB資料庫中)
SNAPSHOT隔離級別在邏輯上與SERIALIZABLE類似
READ COMMITTED SNAPSHOT隔離級別在邏輯上與 READ COMMITTED類似
不過在快照隔離級別下讀操作不需要申請獲得共用鎖,所以即便是數據已經存在排他鎖也不影響讀操作。而且仍然可以得到和SERIALIZABLE與READ COMMITTED隔離級別類似的一致性;如果目前版本與預期的版本不一致,讀操作可以從TEMPDB中獲取預期的版本。
如果啟用任何一種基於快照的隔離級別,DELETE和UPDATE語句在做出修改前都會把行的當前版本複製到TEMPDB中,而INSERT語句不需要在TEMPDB中進行版本控制,因為此時還沒有行的舊數據
無論啟用哪種基於快照的隔離級別都會對更新和刪除操作產生性能的負面影響,但是有利於提高讀操作的性能因為讀操作不需要獲取共用鎖;
5.1SNAPSHOT
SNAPSHOT 在SNAPSHOT隔離級別下,當讀取數據時可以保證操作讀取的行是事務開始時可用的最後提交版本
同時SNAPSHOT隔離級別也滿足前面的已提交讀,可重覆讀,不幻讀;該隔離級別實用的不是共用鎖,而是行版本控制
使用SNAPSHOT隔離級別首先需要在資料庫級別上設置相關選項
在打開的所有查詢視窗中執行以下操作
ALTER DATABASE TEST SET ALLOW_SNAPSHOT_ISOLATION ON;
重置測試數據
IF OBJECT_ID('Orders','U') IS NOT NULL DROP TABLE Orders GO CREATE TABLE Orders (ID INT NOT NULL PRIMARY KEY, Price FLOAT NOT NULL, type INT NOT NULL ); INSERT INTO Orders VALUES(10,10.00,1),(11,11.00,1),(12,12.00,1),(13,13.00,1),(14,14.00,1); GO
在回話1中打開事務,將訂單10的價格加1,並查詢跟新後的價格 BEGIN TRANSACTION UPDATE Orders SET Price=Price+1 WHERE ID=10 SELECT ID,Price,type FROM Orders WHERE ID=10 ---查詢到更新後的價格為11 ---在回話2中將隔離級別設置為SNAPSHOT,並打開事務(此時查詢也不會因為回話1的排他鎖而等待,依然可以查詢到數據) SET TRANSACTION ISOLATION LEVEL SNAPSHOT BEGIN TRANSACTION SELECT ID,Price,type FROM Orders WHERE ID=10 ---查詢到的結果還是回話1修改前的價格,由於回話1在預設的READ COMMITTED隔離級別下運行,SQL SERVER必須在更新前把行的一個副本複製到TEMPDB資料庫中 --在SNAPSHOT級別啟動事務會請求行版本 ---現在在回話1中執行提交事務,此時訂單10的價格為11 COMMIT TRANSACTION ---再次在回話二中查詢訂單10的價格並提交事務,結果還是10,因為事務要保證兩次查詢的結果相同 SELECT ID,Price,type FROM Orders WHERE ID=10 COMMIT TRANSACTION ---此時如果在回話2中重新打開一個事務,查詢到的訂單10的價格則是11 BEGIN TRANSACTION SELECT ID,Price,type FROM Orders WHERE ID=10 COMMIT TRANSACTION /*SNAPSHOT隔離級別保證操作讀取的行是事務開始時可用的最後已提交版本,由於回話1的事務未提交,所以訂單10的最後提交版本還是修改前的價格10,所以回話2讀取到的價格是回話2事務開始前的已提交版本價格10,當回話1提交事務後,回話2重新新建一個事務此時事務開啟前的價格已經是11了,所以查詢到的價格是11,同時SNAPSHOT隔離級別還能保證SERIALIZABLE的隔離級別*/
5.2READ COMMITTED SNAPSHOT
READ COMMITTED SNAPSHOT也是基於行版本控制,但是READ COMMITTED SNAPSHOT的隔離級別是讀操作之前的最後已提交版本,而不是事務前的已提交版本,有點類似前面的READ COMMITTED能保證已提交讀,但是不能保證可重覆讀,不能避免幻讀,但是又比 READ COMMITTED隔離級別多出了不需要獲取共用鎖就可以讀取數據
要啟用READ COMMITTED SNAPSHOT隔離級別同樣需要修改資料庫選項,在回話1,回話2中執行以下操作(執行下麵的操作當前連接必須是資料庫的唯一連接,可以通過查詢已連接當前資料庫的進程,然後KILL掉那些進程,然後再執行該操作,否則可能無法執行成功)
ALTER DATABASE TEST SET READ_COMMITTED_SNAPSHOT ON IF OBJECT_ID('Orders','U') IS NOT NULL DROP TABLE Orders GO CREATE TABLE Orders (ID INT NOT NULL PRIMARY KEY, Price FLOAT NOT NULL, type INT NOT NULL ); INSERT INTO Orders VALUES(10,10.00,1),(11,11.00,1),(12,12.00,1),(13,13.00,1),(14,14.00,1); GO -----在回話1中打開事務,將訂單10的價格加1,並查詢跟新後的價格,並保持事務一直處於打開狀態 BEGIN TRANSACTION UPDATE Orders SET Price=Price+1 WHERE ID=10 --查詢到的價格是11 SELECT ID,Price,type FROM Orders WHERE ID=10 ---在回話2中打開事務查詢訂單10並一直保持事務處於打開狀態(此時由於回話1還未提交事務,所以回話2中查詢到的還是回話1執行事務之前保存的行版本) BEGIN TRANSACTION SELECT ID,Price,type FROM Orders WHERE ID=10 --查詢到的價格還是10 ---在回話1中提交事務 COMMIT TRANSACTION ---在回話2中再次執行查詢訂單10的價格,並提交事務 SELECT ID,Price,type FROM Orders WHERE ID=10 COMMIT TRANSACTION --此時的價格為回話1修改後的價格11,而不是事務之前已提交版本的價格,也就是READ COMMITTED SNAPSHOT隔離級別在同一事務中兩次查詢的結果不一致.
關閉所有連接,然後打開一個新的連接,禁用之前設置的資料庫快照隔離級別選項
ALTER DATABASE TEST SET ALLOW_SNAPSHOT_ISOLATION OFF; ALTER DATABASE TEST SET READ_COMMITTED_SNAPSHOT OFF;
總結
理解了事務隔離級別有助於理解事務的死鎖。
轉自:http://www.cnblogs.com/chenmh/p/3998614.html