可重復讀隔離級別里的可能死鎖


在今天的文章里我想談論下在可重復讀隔離級別(Transaction Isolation Level Repeatable Read)里,當你運行事務時可能引起的2類死鎖。當你使用可重復讀(Repeatable Read)隔離級別設置你的事務,SQL Server對讀取數據把持需要的共享鎖(Shared Locks)直到事務的結束(COMMIT或ROLLBAK)。然后當你嘗試修改讀取的數據(通過UPDATE語句),如果同樣的事務多次並發運行,它會英氣不同類型的死鎖。

循環死鎖(Cycle Deadlock)

循環死鎖是其中一個最常見的死鎖——你就以不同順序訪問資源(例如不同表),最后每個查詢等待另一個。使用可重復讀隔離級別,但你用多個事務只讀寫一個表時,也是有可能引起循環死鎖。我們來看第1個事務的T-SQL代碼: 

 1 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
 2 GO
 3 
 4 BEGIN TRANSACTION
 5 
 6 SELECT * FROM Person.Person
 7 WHERE ModifiedDate = '20030208'
 8 
 9 UPDATE Person.Person
10 SET FirstName = '...'
11 WHERE ModifiedDate = '20030208'
12 
13 SELECT * FROM Person.Person
14 WHERE ModifiedDate = '20030209'
15 
16 UPDATE Person.Person
17 SET FirstName = '...'
18 WHERE ModifiedDate = '20030209'
19 
20 ROLLBACK
21 GO

這是第2個事務的T-SQL代碼:

 1 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
 2 GO
 3 
 4 BEGIN TRANSACTION
 5 
 6 SELECT * FROM Person.Person
 7 WHERE ModifiedDate = '20030209'
 8 
 9 UPDATE Person.Person
10 SET FirstName = '...'
11 WHERE ModifiedDate = '20030209'
12 
13 SELECT * FROM Person.Person
14 WHERE ModifiedDate = '20030208'
15 
16 UPDATE Person.Person
17 SET FirstName = '...'
18 WHERE ModifiedDate = '20030208'
19 
20 ROLLBACK
21 GO

從2個代碼可以看到,2個數據范圍被讀取,最后被更新。如果2個事務在同個時間執行,會發生循環死鎖,因為數據范圍在不同順序里被訪問。

 

當SQL Server開始執行UPDATE語句,必須的更新鎖(Update Lock(U))不能被獲取,因為更新鎖(Update Lock)與來自不同會話已授予的共享鎖(Shared Lock)不兼容。因為在其它會話共享鎖(Shared Lock)已獲得,最后2個UPDATE語句會等待——在一個表上就有了經典的循環鎖!在這個情況下你必須重寫你的代碼來讓這個特定鎖得到解決——以同個順序訪問你的數據范圍。

讀寫/更新死鎖(Read/Update Deadlock)

使用可重復讀隔離級別的第2類死鎖會發生,如果你讀取數據,有意向稍后去更新。我們來看1個簡單事務的T-SQL代碼: 

 1 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
 2 GO
 3 
 4 BEGIN TRANSACTION
 5 
 6 SELECT * FROM Person.Person
 7 WHERE ModifiedDate = '20030208'
 8 
 9 UPDATE Person.Person
10 SET FirstName = '...'
11 WHERE ModifiedDate = '20030208'
12 
13 ROLLBACK
14 GO

為了引起這類死鎖,你只要通過多個會話運行事務。如你從代碼里所見,你甚至不需要訪問不同數據范圍。我們來解釋下這里反生了什么。當這個事務在多個會話並發執行時,對讀取的數據,所有的會話會獲得共享鎖。

因為你在可重復讀里把持共享受(Shared Lock)直到事務的結束(COMMIT或ROLLBACK),下列UPDATE語句不能獲得需要的更新鎖(Update Locks),因為它們已被不同會話里獲得的共享鎖(Shared Locks)所阻塞。死鎖!

這里死鎖可以通過在SELECT語句里使用提示來提前獲取一個更新鎖(Update Lock)。

 1 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
 2 GO
 3 
 4 BEGIN TRANSACTION
 5 
 6 SELECT * FROM Person.Person WITH (UPDLOCK)
 7 WHERE ModifiedDate = '20030208'
 8 
 9 UPDATE Person.Person
10 SET FirstName = '...'
11 WHERE ModifiedDate = '20030208'
12 
13 ROLLBACK
14 GO

因此在剛開始只有一個SELECT語句能獲得必須的更新鎖(Update Locks)(更新鎖(Update Lock)與本身更新鎖(Update Lock)不兼容),繼續使用UPDATE語句,最后會釋放需要的鎖。然后第2個事務會繼續它的SELECT和UPDATE語句。

這里你要使用SQL Server內部在更新執行計划里使用的同樣的技術:在可重復讀隔離級別里,當你讀取的數據稍后有意向去更新時,在讀取階段你需要獲得一個更新鎖來阻止這類死鎖。

小結

從這篇文章里可以看到,如果你使用可重復讀隔離級別是很容易引起各類死鎖。因此當你在這個特定隔離級別里寫事務時,你必須要非常小心。

感謝關注!

參考文章:

https://www.sqlpassion.at/archive/2014/12/15/possible-deadlocks-in-the-isolation-level-repeatable-read/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM