標籤: SQL SEERVER/MSSQL SERVER/SQL/事務隔離級別選項/設定資料庫事務級別
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由於是已提交讀隔離級別,所以不會讀髒資料. */
重置資料
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 本站點所有隨筆都是原創,歡迎大家轉載;但轉載時必須註明文章來源,且在文章開頭明顯處給明連結,否則保留追究責任的權利。 《歡迎交流討論》 |