sqlserver事務隔離

事務是一個工作單元,可能包含查詢和修改數據以及修改數據定義等多個活動。我們可以顯式或隱式的定義事務邊界。可以使用BEGIN TRAN或者BEGIN TRANSACTION語句顯式的定義事務的開始。如果希望提交事務,可以使用COMMIT TRAN語句顯式的定義事務結束。如果不希望提交事務(即要撤銷更改),可以使用ROLLBACK TRAN或者ROLLBACK TRANSACTION語句-摘抄自SQL Server 2012基礎教程。

如果不顯式的標記事務的邊界,默認情況下,SQL Server將把每個單獨語句作爲一個事務,換句話說,默認情況下,每個單獨語句結束後SQL Server自動提交事務。

說到事務就聯想到併發,爲了解決事務中的併發我們則不得不討論下鎖,所以接下來我們首先熟悉一下鎖的模式-排他鎖和共享鎖。

先看看下面的試驗,先開始一個事務處理,這個事務不提交也不回滾,然後另外在打開一個窗口查詢數據。

在上面的事務沒有提交也沒有回滾的情況下,如果另一個程序在執行查詢,那麼就會阻塞,如果沒有設置查詢超時,就會一直等待。

原因:當事務會話對錶進行修改(增刪改)時,事務會請求數據資源的一個排他鎖,這個鎖直到事務完成(提交或者回滾)纔會被取消。當讀取數據時需要獲取該資源上的共享鎖,但是排他鎖和共享鎖不能兼容,此時會導致查詢阻塞不得不進行等待。

就上面的試驗,第一個事務擁有排它鎖,在事務沒有完成的情況下,第二個事務攜帶共享鎖來訪問表,此時就必須等待第一個事務完成後才能進行查詢。但是反過來沒問題,就是先有查詢的事務進行查詢,但是沒完成事務,然後第二個事務來進行修改

並且能正常完成事務(這主要是因爲在默認的隔離級別下,共享鎖在完成數據的讀取後就釋放了,而不是在事務會話完成後釋放)。注意,這個鎖是針對修改的數據的行的,不是針對整個表。比如上面修改的是id=1的行,但如果我查詢id=2的行,那麼就不會出現阻塞。

因此,在事務處理中,如果遇到併發的情況,會有可能導致程序阻塞。

 

鎖的隔離級別

SQL Server支持4個基於悲觀併發控制的傳統隔離級別:

  READ UNCOMMITTED、

  READ COMMITTED(企業內部部署的SQL Server實例的默認方式)、

  REPEATABLE READ、

  SERIALIZABLE。

SQL Server還支持兩種基於併發控制(行版本)的隔離級別:

  SNAPSHOT

  READ COMMITTED SNAPSHOT(SQL Database的默認方式)

在某種意義上,SNAPSHOT和READ COMMITTED SNAPSHOT分別是READ COMMITTED和SERIALIZABLE的樂觀併發對應方式。

 

第一類丟失更新:撤銷一個事務時,把其他事務已提交的更新數據覆蓋。

髒讀:一個事務讀到另一事務未提交的更新數據。

虛讀:一個事務讀到另一事務已提交的新插入的數據。

不可重複讀:一個事務讀到另一事務已提交的更新數據。

第二類丟失更新:這是不可重複讀中的特例,一個事務覆蓋另一事務已提交的更新數據。

 

READ UNCOMMITTED隔離級別

READ UNCOMMITTED是最低隔離級別,在該隔離級別中,讀取者不需要請求共享鎖,不要求共享鎖的讀取者從不會與持有排他鎖的寫入者發生衝突,這意味着讀取者可以讀取寫入者未提交的更改即髒讀,也就是說,讀取者不會干擾要求了排他鎖的寫入者,在該隔離級別下運行的寫入者更改數據時,讀取者可以讀取數據。

 默認情況下,上面的語句執行成功的,但是事務沒有執行完成,這時排它鎖是存在的,下面在另一個事務中設置隔離級別爲READ UNCOMMITTED運行如下代碼,可以看到,這個事務能查詢到上一個事務沒有提交的修改

 

這時候我們再將第一個事務回滾(只執行紅框中的語句)。

 

可以看到在隔離級別是READ UNCOMMITTED的情況下,事務併發會出大問題的(第二個事務獲取了一個錯誤的數據)。

 

READ COMMITTED隔離級別

 該隔離級別僅允許讀取已提交的更改。它通過要求讀取者獲得一個共享鎖來防止未提交的讀取,也就是說,如果一個寫入者持有了排他鎖,讀取者的共享鎖請求將會與寫入者的排它鎖衝突,此時必須等待,一旦寫入者提交了事務,讀取者就可以獲得它的共享鎖,所以它必然是隻讀取提交後的修改。這個已經在本文最開始的時候試驗過了。

這裏再次試驗一下:虛讀

在第一個事務中,第一次查詢獲得了6條數據,這時候事務未提交,開始第二個事務

成功提交後,繼續執行第一個事務的第二次查詢,

 

不可重複讀和上面的試驗類似,只是變成的修改。但是對於虛讀,我一直沒理解真實的場景中有什麼問題。看了很多介紹,都說在同一個事務中,兩次相同條件的查詢獲得的結果不同,就是虛讀或者不可重複讀,但是這樣的情況在實際場景

中是很合理的啊。所以有點不明白。很多介紹中說是,在同一個事務中,兩次查詢獲得的結果不一致(注意,對於虛讀的定義是兩次查詢,包括不可重複讀也是,但是很多介紹銀行取款存款的例子中,只有一次查詢),會導致用戶無法確定該

用哪個結果,但是我們假設兩次讀取的數據都是真實的,那麼以後面讀取的數據爲準,這樣是符合事實邏輯的啊,

就是不明白這種情況爲什麼會有問題,在實際場景中也想不出這樣的問題在哪裏。思來想去,覺得在真實場景中因爲這虛讀或不重複讀出現問題的可能就是一種很極端的情況:

在特定時刻下,需要對錶中id<10的數據進行一次刪除或者其他的一些處理吧,反正就是要對此刻以前在表中滿足一定條件的數據進行處理。因此在事務1中,第一次查詢獲得了此刻,在表中滿足條件的數據。然後這時,另一個事務添加了

一條也滿足這個條件的數據(或者是對滿足條件的數據進行了修改)。那麼在繼續進行事務1的處理(比如刪除或者其他的一些操作),就會將事務2添加的數據(或者修改的數據)一併給處理了,但是,事務2添加的數據是在特定時刻以後纔出現的,是不能

被處理的。當然這樣的情況是確實有可能發生,但是爲了避免這樣的情況,相信在寫sql語句的時候有很多辦法來避免的。

在此隔離級別下要注意一種情況,就是:事務1有兩個相同的查詢,在剛執行完第一次查詢後,事務2在這個時候對查詢的數據進行了修改,並且提交了。那麼事務1的第二個查詢獲取的數據就是新的數據。這一切看起來很正常,但是要注意的是,在事務1的兩個

相同查詢中獲得了不同的數據,這種情況看似合理,但實則是每次讀取到的值可能會有所不同,這種現象被稱爲不可重複讀取或不一致解析,即既是虛讀,又是不可重複讀取。

更進一步則是在事務1中的第二次查詢後,根據查詢的結果來修改數據,並且成功提交了,這就是第二類更新丟失了。

REPEATABLE READ隔離級別

如果要解決虛讀的問題,至少將隔離級別調到repeatable read。

看下面的試驗,將隔離級別設置爲repeatable read,在執行查詢後,沒有完成事務

接着在另一個窗口中執行下面的修改事務,會看到事務一直在等待執行。這時候將上面的事務如果還有第二次讀取,那麼讀取的結果和第一次肯定是相同的,因爲沒有被其他事務修改。提交或回滾後,下面的事務就立即執行 

 通過這個試驗可以看到, REPEATABLE READ隔離成功解決了不可重複讀的問題,同時也解決了第二類更新丟失的問題。

 但是這個隔離會出現死鎖的情況,看下面的試驗:

事務中先執行查詢,再執行修改

 

可以看到,這裏死鎖了,一直在執行,即使先修改後查詢也是一樣。原因:在這種隔離下,查詢的時候會獲取一個共享鎖,這個共享鎖必須直到事務完成後才釋放(前面兩種隔離不會這樣),然後遇到了修改語句,需要獲取一個排它鎖,但是排他鎖和

共享鎖不能同時存在,因此就一直等着,死鎖了。(前面兩種隔離,如果在同一個事務中,先有修改,再有查詢,那麼也會造成死鎖)

 

還有一個問題,看下面的試驗:

先執行事務:執行紅框中的部分,表示這個事務只執行了一個查詢,查詢結果如下

然後第二個事務來了:這個能成功執行,因爲第一個事務的共享鎖是鎖定了id爲1到6的行,但是id=8的行沒有鎖住,所以可以獲得這行的排它鎖。(不知道這樣理解對不對)

然後繼續執行第一個事務的後面部分,查詢結果

 

可以看到,同一個事務的兩次同樣的查詢,獲得了不同的結果,這就是虛讀。

 

ERIALIZABLE隔離級別

爲了防止幻影讀取,需要將隔離級別提升爲SERIALIZABLE,最重要的部分是SERIALIZABLE隔離級別的行爲類似於REPEATABLE READ即它要求讀取者獲取一個共享鎖來進行讀取,並持有鎖到事務結束,但是SERIALIZABLE隔離級別添加了另外一個方面-在邏輯上,該隔離級別要求讀取者鎖定查詢篩選所限定的鍵的整個範圍。這意味着讀取者鎖定的不僅是查詢篩選限定的現有行,也包括將來行,或者準確地說,它會阻止其他事務嘗試添加讀取者查詢篩選限定的行。

基於行版本的隔離級別

在SQL Server中存在兩種基於行版本控制技術的隔離級別:SNAPSHOT、READ COMMITTED SNAPSHOT。將提交行之前的版本存儲在tempdb中,SNAPSHOT隔離級別在邏輯上類似於SERIALIZABLE隔離級別,READ COMMITTED SNAPSHOT隔離級別類似於READ COMMITTED隔離級別,但是,讀取者使用基於行版本控制的隔離級別並不不會發出共享鎖,所以在請求的數據以排他鎖鎖定時它們不會等待,讀取者仍舊會獲得類似於SERIALIZABLE和READ  COMMITTED的一致性級別,如果當前版本不是它們希望看到的版本,那麼SQL Server會給讀取者提供一個較舊的版本。

如果啓用了任何基於快照的隔離級別,在修改tempdb之前,DELETE和UPDATE語句需要複製行的版本,對於INSERT語句則不需要再tempdb中版本化,因爲它不存在早期的版本,但需要注意的是,啓用任何基於行版本控制的隔離級別對於數據更新和刪除的性能可能會有負面影響,由於它們不會獲取共享鎖,並且哎數據被以排他方式鎖定或是數據版本不是所期望的版本時不需要等待,因此對於讀取者的性能通常會有所改善。

 

SNAPSHOT隔離級別

要想在企業部署的SQL Server實例中允許事務以SNAPSHOT隔離級別工作,首先需要在查詢窗口執行以下代碼打開快照隔離級別。如下:tsql2012是數據庫名

ALTER DATABASE TSQL2012 SET ALLOW_SNAPSHOT_ISOLATION ON

下面通過試驗來看看SNAPSHOT的行爲:

執行下面的 事務,事務未完成。這如果沒有執行剛纔的alert語句,這個事務是會死鎖的,原因上面已經解釋過了

 

然後另一個事務:

 

從查詢結果看,獲取的數據是第一個事務修改前的數據。然後現在將第一個事務提交,再來看看第二個事務的後半部分的查詢結果

 看到,只要第二個事務沒有提交,那麼它不管進行多少次查詢,獲得的結果都是一樣的

 

 

 

 SNAPSHOT隔離級別可以防止更新衝突,但不會像REPEATABLE READ和SERIALIZABLE隔離級別那樣產生死鎖,SNAPSHOT隔離級別的事務失敗,表明檢測到了更新衝突,SNAPSHOT隔離級別通過檢查存儲的版本來檢測更新衝突,它可以發現在事務的讀取和寫入之間是否有另一個事務修改了數據。

READ_COMMITTED_SNAPSHOT隔離級別

該隔離級別也是基於行版本控制,它與SNAPSHOT隔離級別區別在於,讀取者獲得是【語句】啓動時可用的最後提交的行版本,而不是【事務】啓動時可用的最後提交的行版本,READ_COMMITTED_SNAPSHOT也不會檢測更新衝突,導致類似於READ COMMITTED隔離級別,但在所請求資源以排他鎖鎖定時,不會請求共享鎖並且不會等待。在企業內部部署的SQL Server中要想啓動READ_COMMITTED_SNAPSHOT隔離級別,需要打開唯一會話來設置,否則無法進行啓用(啓用該隔離級別實際上是將READ COMMITTED隔離級別在語義上改變爲READ_COMMITTED_SNAPSHOT隔離級別)。要啓用該隔離級別,須執行如下語句:

ALTER DATABASE TEST SET SINGLE_USER WITH ROLLBACK IMMEDIATE   --數據庫置爲單用戶模式
ALTER DATABASE TEST SET READ_COMMITTED_SNAPSHOT ON

用這種隔離級別將事務處理完成後,記得要將數據庫重置爲多用戶模式,並且關閉這種隔離

ALTER DATABASE TEST set MULTI_USER

ALTER DATABASE TEST SET READ_COMMITTED_SNAPSHOT OFF

這個在試驗的時候,沒法在同一個窗口打開兩個不同的事務,所以沒法驗證了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章