SQL Server 事務隔離級別詳解

標籤: SQL SEERVER/MSSQL SERVER/SQL/事務隔離級別選項/設置數據庫事務級別數據庫

SQL 事務隔離級別併發

概述性能

     隔離級別用於決定若是控制併發用戶如何讀寫數據的操做,同時對性能也有必定的影響做用。測試

步驟spa

事務隔離級別經過影響讀操做來間接地影響寫操做;能夠在回話級別上設置事務隔離級別也能夠在查詢(表級別)級別上設置事務隔離級別。
事務隔離級別總共有6個隔離級別:
READ UNCOMMITTED(未提交讀,讀髒),至關於(NOLOCK)
READ COMMITTED(已提交讀,默認級別)
REPEATABLE READ(能夠重複讀),至關於(HOLDLOCK)
SERIALIZABLE(可序列化)
SNAPSHOT(快照)
READ COMMITTED SNAPSHOT(已經提交讀隔離)
對於前四個隔離級別:READ UNCOMMITTED<READ COMMITTED<REPEATABLE READ<SERIALIZABLE
隔離級別越高,讀操做的請求鎖定就越嚴格,鎖的持有時間久越長;因此隔離級別越高,一致性就越高,併發性就越低,同時性能也相對影響越大.
3d

獲取事務隔離級別(isolation level)版本控制

DBCC USEROPTIONS 

設置隔離code

設置回話隔離
SET TRANSACTION ISOLATION LEVEL <ISOLATION NAME>
--注意:在設置回話隔離時(REPEATABLE READ)兩個單詞須要用空格間隔開,可是在表隔離中能夠粘在一塊兒(REPEATABLEREAD)

設置查詢表隔離
SELECT ....FROM <TABLE> WITH (<ISOLATION NAME>) 

1.READ UNCOMMITTEDblog

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因爲是已提交讀隔離級別,因此不會讀髒數據.
*/

重置數據

UPDATE Orders 
SET Price=10
WHERE ID=10

注意:可是因爲READ COMMITTED讀操做一完成就當即釋放共享鎖,讀操做不會在一個事務過程當中保持共享鎖,也就是說在一個事務的的兩個查詢過程之間有另外一個回話對數據資源進行了更改,會致使一個事務的兩次查詢獲得的結果不一致,這種現象稱之爲不可重複讀.

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;

 

總結

   理解了事務隔離級別有助於理解事務的死鎖。

 

 

備註:

    做者:pursuer.chen

    博客:http://www.cnblogs.com/chenmh

本站點全部隨筆都是原創,歡迎你們轉載;但轉載時必須註明文章來源,且在文章開頭明顯處給明連接,不然保留追究責任的權利。

《歡迎交流討論》

相關文章
相關標籤/搜索