--摘自http://support.microsoft.com/kb/169960/zh-cn
INF :在 SQL Server 中分析並避免死鎖
概要
Microsoft SQL Server
使用鎖來維護事務完整性和數據庫一致性。與任何關係數據庫系統一樣,鎖定可能導致用戶之間出現死鎖。
例如,假設用戶 1
(或連接 1
)持有數據項“A”
的鎖,並希望獲得數據項“B”
的鎖。用戶 2
持有數據項“B”
的鎖,而且現在希望獲得數據項“A”
的鎖。在此 SQL Server
情況中,用戶 1
或用戶 2
將爲死鎖犧牲品,其他用戶將被授予所請求的鎖。
在 SQL Server
中,應用程序開發人員可以通過使用
SET DEADLOCK_PRIORITY
來確定哪個連接將是死鎖犧牲品的候選項。如果開發人員不爲死鎖指定優先級,SQL Server
將通過選擇完成循環鎖鏈的進程來選擇死鎖犧牲品。
數據庫應用程序系統在從一個關係數據庫轉入另一個關係數據庫時,可能會根據關係數據庫系統實現的不同而具有不同的行爲方式。查找行爲更改的一個方面是鎖定。本文介紹在 SQL Server
中如何分析死鎖以及可用來避免死鎖的技術。
更多信息
本文重點介紹如何使用跟蹤標誌 T1204 的輸出來分析死鎖。設置跟蹤標誌 T1204 之後,SQL Server 會在發生死鎖時打印有關死鎖的信息。要使用此跟蹤標誌,請在命令提示符處使用以下命令來啓動 SQL Server :
sqlservr -c -T1204
跟蹤結果將發送至控制檯窗口,但如果設置了跟蹤標誌 T3605
,則會將跟蹤輸出發送至錯誤日誌。
當兩個連接以相反順序更新表時,會發生死鎖。例如,一個連接先插入到表“example1”
中,然後插入到“example2”
中,而另一個連接先插入到表“example2”
中,然後插入到事務中的“example1”
中。通過示例可很好地說明如何避免死鎖。
下面是用於爲此示例創建表的 SQL
語句:
create table example1 (column1 int, column2 char(20), column3 char(50))
go
create table example2 (column1 int, column2 char(20), column3 char(50))
go
declare @lvar int
select @lvar = 0
while @lvar < 500
begin
insert into example1 values (@lvar, 'AAA', 'CCC')
insert into example2 values (@lvar, 'AAA', 'CCC')
select @lvar = @lvar + 1
end
go
create unique clustered index ex1ind1 on example1 (column1, column2)
with fill factor = 90, PAD_INDEX
go
create unique clustered index ex2ind1 on example2 (column1, column2)
with fill factor = 90, PAD_INDEX
go
示例 1 :順序相反的表插入
在此示例中,以相反順序插入兩個表,並且發生了死鎖。當兩個或更多個連接以相反順序對錶執行更新或刪除時,也會發生死鎖。
Connection1 > BEGIN TRANSACTION
Connection2 > BEGIN TRANSACTION
Connection1 > INSERT INTO example1 VALUES (100, 'AAAA', 'CCC')
Connection2 > INSERT INTO example2 VALUES (200, 'AAAB', 'CCC')
Connection2 > INSERT INTO example1 VALUES (200, 'AAAB', 'CCC')
此處,Connection1
可能會阻塞 Connection2
,因爲 Connection2
正在插入的行可能與 Connection1
已插入行並持有鎖的位置在同一頁上。
Connection1 > INSERT INTO example2 VALUES (100, 'AAAA', 'CCC')
此處,Connection2
可能會阻塞 Connection1
,因爲 Connection1
正在插入的行可能與 Connection2
已插入行並持有鎖的位置在同一頁上。這會引起死鎖。
下面是發生死鎖時跟蹤標誌 1204
的輸出:
97/04/20 11:51:57.88 spid13 *** DEADLOCK DETECTED with spid 14 ***
spid 13 requesting EX_PAGE (waittype 0x8005), blocked by:
EX_PAGE: spid 14, dbid 6, page 0x188, table example2, indid 0x1
pcurcmd INSERT(0xc3), input buffer: INSERT INTO example2 VALUES (100,
'AAAA', 'CCC')
spid 14 waiting for EX_PAGE (waittype 0x8005), blocked by:
EX_PAGE: spid 13, dbid 6, page 0x180, table example1, indid 0x1
pcurcmd INSERT(0xc3), input buffer: INSERT INTO example1 VALUES (200,
'AAAB', 'CCC')
VICTIM: spid 13, pstat 0x0000 , cputime 30
死鎖跟蹤的每行都會向用戶說明有關死鎖的更多信息。Connection1
是 spid 13
,Connection2
是 spid 14
(通過使用 sp_who
系統存儲過程可以確定與連接關聯的 spid
)。
>> 97/04/20 11:51:57.88 spid13 *** DEADLOCK DETECTED with spid 14 ***
The deadlock was detected between spid 13 and spid 14.
>> spid 13 requesting EX_PAGE (waittype 0x8005), blocked by:
>> EX_PAGE: spid 14, dbid 6, page 0x188, table example2, indid 0x1
>> pcurcmd INSERT(0xc3), input buffer: INSERT INTO example2 VALUES
(100, 'AAAA', 'CCC')
Spid 13
正在請求 EX_PAGE
鎖,並被 spid 14
阻塞,後者已經持有 dbid 6
中表 example2
中的頁 0x188
的
EX_PAGE
鎖。該鎖保留在屬於羣集索引的頁上。
Indid Value Description
-------------------------------------
0 Data page if there is no clustered index, or the
leaf page of a clustered index if there is one
1 Non-leaf page of the clustered index page
255 Text/image page
Any other value Non-clustered secondary index
spid 13
執行的當前命令爲 INSERT
,跟蹤給出輸入緩衝區的一部分。
>> spid 14 waiting for EX_PAGE (waittype 0x8005), blocked by:
>> EX_PAGE: spid 13, dbid 6, page 0x180, table example1, indid 0x1
>> pcurcmd INSERT(0xc3), input buffer: INSERT INTO example1 VALUES
(200, 'AAAB', 'CCC')
Spid 14
正在等待 EX_PAGE
鎖並被 spid 13
阻塞,後者已在同一頁上持有 EX_PAGE
鎖。
>> VICTIM: spid 13, pstat 0x0000 , cputime 30
SQL Server has chosen spid 13 as the deadlock victim.
下面是對該跟蹤中各種鎖的含義進行的說明:
SH_INT
和 EX_INT
它們是由於鎖管理器不瞭解不同類型的項目(在此示例中爲頁和表)之間的關係,而在可以獲得較低級的鎖(例如,頁)之前獲得的較高級的項目(例如,表)的意圖鎖。如果在獲得頁的 EX_PAG
鎖之前未獲得表的 EX_INT
鎖,則其他用戶可以獲得同一個表的 EX_TAB
鎖,而且鎖管理器將不會知道存在衝突。目前,SQL Server
僅具有針對表的意圖鎖。存在兩種意圖鎖:共享 (SH_INT)
和獨佔 (EX_INT)
鎖。
EX_PAGE
這是一個獨佔頁鎖,可在頁因爲 DELETE
、UPDATE
或 INSERT
語句而更新並且已禁用插入行級鎖定 (IRL)
時獲得。
UP_PAGE
這是一個更新頁鎖,可在掃描頁並且優化器知道頁將更新(或使用了 UPDLOCK
提示)時用來代替共享頁鎖。
PR_EXT
、NX_EXT
、UPD_EXT
和 EX_EXT
在分配或取消分配磁盤空間時可以獲得這些鎖。在從現有範圍內分配或取消分配頁時可獲得 UPD_EXT
,其他鎖則在分配或取消分配整個範圍時使用。
IX_PAGE
和 LN_PAGE
這些是 IRL
鎖。IX_PAGE
是頁的 intent-to-do-row-locking
鎖。當正在執行 IRL
的頁需要拆分時,會獲得 LN_PAGE
。
RLOCK
和 XRLOCK
這些短期鎖可在遍歷索引 b
樹時獲得。此類鎖有兩種類型:共享
(RLOCK)
和獨佔 (XRLOCK)
鎖。共享鎖在掃描期間獲得,而獨佔鎖則在更新期間針對索引頁獲得。
EX_TAB
這是在 SQL Server
優化器確定表掃描是解決更新查詢的最有效方法時(例如,當表中沒有索引時)出現的獨佔表鎖。當用 TABLOCKX
提示鎖定表時或 SQL Server
將表的頁鎖升級爲表鎖時,也會出現 EX_TAB
鎖。
SH_TAB
這是當優化器假定將掃描表的大部分內容(或頁鎖定升級)或使用了 TABLOCK
提示時將使用的共享表鎖。
如果兩個連接按以下順序更新表,則可避免前面的死鎖示例:
Connection1 > BEGIN TRANSACTION
Connection2 > BEGIN TRANSACTION
Connection1 > INSERT INTO example1 VALUES (100, 'AAAA', 'CCC')
Connection2 > INSERT INTO example1 VALUES (200, 'AAAB', 'CCC')
Connection2 > INSERT INTO example2 VALUES (200, 'AAAB', 'CCC')
Connection1 > INSERT INTO example2 VALUES (100, 'AAAA', 'CCC')
示例 2 :對同一個表的不同部分的插入
如果兩個連接以相反順序插入到同一個表的不同部分中,但行有共享的頁,則也會出現此死鎖。例如:
Connection1 > BEGIN TRANSACTION
Connection2 > BEGIN TRANSACTION
Connection1 > INSERT INTO example1 VALUES (100, 'AAAA', 'CCC')
Connection2 > INSERT INTO example1 VALUES (400, 'AAAB', 'CCC')
Connection1 > INSERT INTO example1 VALUES (400, 'AAAA', 'CCC')
在此示例表中,在 example1
表的第一列中存在一個羣集索引。對於第一列而言,具有相同值的行將傾向於位於同一頁中。在該示例中,Connection1
插入的第二行很可能與 Connection2
插入的第一行位於同一頁上,因爲它們都具有羣集索引值 400
。這會導致 Connection2
阻塞 Connection1
。
Connection2 > INSERT INTO example1 VALUES (100, 'AAAB', 'CCC')
現在,Connection1
也可能阻塞 Connection2
,從而導致死鎖。以下爲死鎖跟蹤信息:
97/04/20 12:56:01.40 spid16 *** DEADLOCK DETECTED with spid 15 ***
spid 16 requesting EX_PAGE (waittype 0x8005), blocked by:
EX_PAGE: spid 15, dbid 6, page 0x2c5, table example1, indid 0
pcurcmd INSERT(0xc3), input buffer: INSERT INTO example1 VALUES (100,
'AAAB', 'CCC')
spid 15 waiting for EX_PAGE (waittype 0x8005), blocked by:
EX_PAGE: spid 16, dbid 6, page 0x8bd, table example1, indid 0
pcurcmd INSERT(0xc3), input buffer: INSERT INTO example1 VALUES (400,
'AAAA', 'CCC')
VICTIM: spid 16, pstat 0x0000 , cputime 130
Spid 16
對頁 0x2c5
的
EX_PAGE
鎖的請求被 spid 15
阻塞,spid
15
在執行第一次插入後已持有頁 0x2c5
的
EX_PAGE
鎖。spid 15
在等待頁 0x8db
的 EX_PAGE
鎖時也被 spid 16
阻塞,從而導致死鎖。
通過使用以下命令爲表 example1
啓用 IRL
可以避免此死鎖:
sp_tableoption 'example1', 'insert row lock', true
示例 3 :使用 IRL 的插入
當兩個或更多個用戶只執行插入操作時,IRL 允許這些用戶共享頁,這通常會提高吞吐量。但是,啓用 IRL 不是總能夠減少死鎖。在某些情況下,IRL 可能會引入死鎖。
Connection1 > BEGIN TRANSACTION
Connection2 > BEGIN TRANSACTION
Connection1 > INSERT INTO example1 VALUES (100, 'AAAA', 'CCC')
Connection2 > INSERT INTO example1 VALUES (105, 'AAAB', 'CCC')
啓用 IRL
後,兩個連接都將持有包含兩個新行的頁的 IX_PAGE
鎖。如果禁用 IRL
,Connection1
將獲得 EX_PAGE
鎖,Connection2
將立即被阻塞。
Connection2 > UPDATE example1 SET column3 = 'CCCB' where column1 = 105
and column2 = 'AAAB'
此處,Connection2
需要獨佔頁鎖以執行 UPDATE
語句,該鎖與 Connection1
的 IX_PAGE
鎖不兼容。因此,Connection2
將等待。
Connection1 > UPDATE example1 SET column3 = 'CCCA' where column1 = 100
and column2 = 'AAAA'
現在,Connection1
可能被 Connection2
阻塞,從而導致死鎖。以下爲死鎖跟蹤信息:
97/04/20 15:13:50.07 spid17 *** DEADLOCK DETECTED with spid 18 ***
spid 17 requesting UP_PAGE (waittype 0x8007), blocked by:
IX_PAGE: spid 18, dbid 6, page 0x2c5, table example1, indid 0
pcurcmd UPDATE(0xc5), input buffer: UPDATE example1 SET column3 =
'CCCA' where column1 = 100 and column2 = 'AAAA'
spid 18 waiting for UP_PAGE (waittype 0x8007), blocked by:
IX_PAGE: spid 17, dbid 6, page 0x2c5, table example1, indid 0
pcurcmd UPDATE(0xc5), input buffer: UPDATE example1 SET column3 =
'CCCB' where column1 = 105 and column2 = 'AAAB'
VICTIM: spid 17, pstat 0x0000 , cputime 20
Spid 17 (Connection1)
正在等待 UP_PAGE
鎖,這是獲取獨佔頁鎖的第一步。Spid 17
被 spid 18
阻塞,後者持有頁 0x2c5
的 IX_PAGE
鎖。Spid
18
正在等待同一頁的 UP_PAGE
鎖,並被 spid
17
持有的 IX_PAGE
鎖阻塞。由於 IX_PAGE
鎖可共享,而 UP_LOCK
不可共享,因此這會導致死鎖。在第一次插入期間,兩個 spid
都獲得了同一頁的 IX_PAGE
鎖,後來,它們嘗試將該鎖升級爲 UP_PAGE
鎖,由於 UP_PAGE
鎖是獨佔的,因此該升級無法實現。
避免死鎖的一種方法是將更新的值直接插入到表中,而不是插入並隨後更新同一事務中的行。如果無法實現這種方法,則使用以下命令來禁用 IRL
將有助於避免死鎖:
sp_tableoption 'example1', 'insert row lock', false
示例 4 :向同一頁上的行執行的插入
當兩個 spid 所處理的行不同但屬於同一頁時,也會產生死鎖。
Connection1 > BEGIN TRANSACTION
Connection2 > BEGIN TRANSACTION
Connection1 > INSERT INTO example1 VALUES (100, 'AAAA', 'CCC')
Connection2 > INSERT INTO example1 VALUES (400, 'AAAB', 'CCC')
Connection1 > UPDATE example1 SET column3 = 'CCCA' where column1 = 405
and column2 = 'AAAA'
此處,Connection1
可能被 Connection2
阻塞。因爲 Connection1
要更新 Connection2
已插入一行的頁中的一行,因此可能會出現這種情況。
Connection2 > UPDATE example1 SET column3 = 'CCCB' where column1 = 105
and column2 = 'AAAB'
此處,Connection2
也可能被 Connection1
阻塞,這將導致死鎖。當 Connection2
要更新 Connection1
已插入一行的頁中的一行時,可能會出現這種情況。以下爲死鎖跟蹤信息:
97/04/20 15:48:21.18 spid20 *** DEADLOCK DETECTED with spid 19 ***
spid 20 requesting UP_PAGE (waittype 0x8007), blocked by:
EX_PAGE: spid 19, dbid 6, page 0x2c4, table example1, indid 0
pcurcmd UPDATE(0xc5), input buffer: UPDATE example1 SET column3 =
'CCCB' where column1 = 105 and column2 = 'AAAB'
spid 19 waiting for UP_PAGE (waittype 0x8007), blocked by:
EX_PAGE: spid 20, dbid 6, page 0xc48, table example1, indid 0
pcurcmd UPDATE(0xc5), input buffer: UPDATE example1 SET column3 =
'CCCA' where column1 = 405 and column2 = 'AAAA'
VICTIM: spid 20, pstat 0x0000 , cputime 60
通過將行分佈到不同頁可以避免此死鎖。執行此操作的一種方法是用較大的填充因子對此表重新創建羣集索引。下面是以 50%
爲填充因子創建羣集索引的語句:
create unique clustered index ex1ind1 on example1 (column1, column2)
with fill factor = 50, PAD_INDEX
此語句將創建羣集索引,同時將頁的一半保留爲空,其中包括非葉級羣集索引(因爲
PAD_INDEX
選項)。表佔用空間爲實際大小的兩倍,每頁的行數是實際行數的一半。
不保留表的填充因子,只在創建索引期間用指定的填充因子重新組織表。隨着時間的變化,每頁的行將發生變化,不再是索引創建期間指定的填充因子。發生這一情況時,最好用期望的填充因子重新創建羣集索引。
避 免前面的死鎖情況的另一種解決方案是用虛擬列(例如,dummy1 char(255)
)來填充表。這會增大行的大小,並導致每頁的行數更少(最少可達每頁一行)。由於此類型的填充可隨時間變化而維護,因此無需重新創建羣
集索引來維護填充(但由於其他原因您可能想重新創建羣集索引)。此方法的缺點是要在虛擬字段上浪費存儲空間。
示例 5 :填充行
填充行會導致每頁中的行數更少(因此,死鎖也更少),但這並不能完全消除死鎖。
在此示例表中,example1
進行了填充,使每頁佔用一行。下面是用於爲此示例創建表的語句:
create table example1 (column1 int, column2 char(20), column3 char(50),
dummy_column4 char (255), dummy_column5 char (255), dummy_column6 char
(255))
go
create unique index ex1ind5 on example1 (column3, column2, column1,
dummy_column4, dummy_column5, dummy_column6) with fill factor = 85
go
Connection1 > BEGIN TRANSACTION
Connection2 > BEGIN TRANSACTION
Connection1 > INSERT INTO example1 VALUES (100, 'AAAA', 'CCC', ' ', ' ',
' ', ' ')
Connection2 > INSERT INTO example1 VALUES (400, 'AAAB', 'CCC', ' ', ' ',
' ', ' ')
Connection1 > UPDATE example1 SET column3 = 'CCCA' where column1 = 401
and column2 = 'AAAA'
此處,Connection1
在更新行時被 Connection2
阻塞。由於 SQL Server
必須維護頁鏈指針,因此它會鎖定上一頁、下一頁以及正在更新的頁。由於 Connection2
持有上一頁的鎖,因此 Connection1
必須等待,直到 Connection2
提交事務爲止。
Connection2 > UPDATE example1 SET column3 = 'CCCB' where column1 = 101
and column2 = 'AAAB'
此處,Connection2
被
Connection1
阻塞,因爲它必須鎖定上一頁,該頁當前被 Connection1
鎖定。結果就是出現死鎖。以下爲死鎖跟蹤信息:
spid 20 requesting UP_PAGE (waittype 0x8007), blocked by:
EX_PAGE: spid 19, dbid 6, page 0x12b5, table example1, indid 0
pcurcmd UPDATE(0xc5), input buffer: UPDATE example1 SET column3 =
'CCCB' where column1 = 101 and column2 = 'AAAB'
spid 19 waiting for UP_PAGE (waittype 0x8007), blocked by:
EX_PAGE: spid 20, dbid 6, page 0x1531, table example1, indid 0
pcurcmd UPDATE(0xc5), input buffer: UPDATE example1 SET column3 =
'CCCA' where column1 = 401 and column2 = 'AAAA'
VICTIM: spid 20, pstat 0x0000 , cputime 300
通過在插入、更新或刪除的行之間插入虛構行可以避免此死鎖。例如,如果
Connection1
對行 pk = 1
進行處理(插入、更新或刪除),Connection2
對行 pk = 5
進行處理,則在這兩行之間插入行(如包含 pk = 3
的行)將避免死鎖。此方法也會增加表的大小,但對於對應用程序至關重要的那些隊列表而言,這可能是最佳的解決方案。
示例 6 :非羣集索引
在某些情況下,非羣集的輔助索引可能會引入死鎖。在此示例中,輔助索引的維護引入了死鎖。
下面是用來爲此示例創建輔助索引的語句:
create index ex1ind2 on example1 (column3) with fill factor = 90,
PAD_INDEX
Connection1 > BEGIN TRANSACTION
Connection2 > BEGIN TRANSACTION
Connection1 > INSERT INTO example1 VALUES (100, 'AAAA', 'CCBA', ' ', '
', ' ', ' ')
Connection2 > INSERT INTO example1 VALUES (300, 'AAAB', 'CCCZ', ' ', '
', ' ', ' ')
Connection2 > UPDATE example1 SET column3 = 'CCBA' where column1 = 105
此處,Connection2
可能被 Connection1
阻塞,因爲 Connection1
可能持有 Connection2
需要更新的輔助非羣集索引頁的鎖。
Connection1 > UPDATE example1 SET column3 = 'CCCZ' where column1 = 305
此處,Connection1
可能被 Connection2
阻塞,從而導致死鎖。當 Connection1
正在等待鎖以更新 Connection2
已在其中插入並持有該頁的鎖的非羣集輔助索引時,會出現這種情況。以下爲此死鎖示例的死鎖跟蹤信息:
97/04/20 19:05:38.75 spid11 *** DEADLOCK DETECTED with spid 12 ***
spid 11 requesting EX_PAGE (waittype 0x8005), blocked by:
EX_PAGE: spid 12, dbid 6, page 0x112f, table example1, indid 0x2
pcurcmd UPDATE(0xc5), input buffer: UPDATE example1 SET column3 =
'CCCZ' where column1 = 305
spid 12 waiting for EX_PAGE (waittype 0x8005), blocked by:
EX_PAGE: spid 11, dbid 6, page 0x1108, table example1, indid 0x2
pcurcmd UPDATE(0xc5), input buffer: UPDATE example1 SET column3 =
'CCBA' where column1 = 105
VICTIM: spid 11, pstat 0x0000 , cputime 50
通過刪除輔助索引可以避免此死鎖。不能通過填充索引來實現每頁包含一行,因此只能通過消除非羣集輔助索引或修改應用程序來避免這一情況。
死鎖可能發生於兩個以上的連接中,在這種情況下,死鎖跟蹤會列出死鎖中涉及的 spid
以及彼此衝突的鎖。死鎖可能發生於 RLOCK
和 XRLOCK
鎖中,這些鎖在索引遍歷期間獲得。也可能由於範圍鎖(PR_EXT
、NX_EXT
、UPD_EXT
和 EX_EXT
)而出現死鎖。
要了解有關分析死鎖的其他信息,可以啓用以下跟蹤標誌:
T1200
打印所有鎖請求/
釋放信息(在請求/
釋放發生時),無論是否出現死鎖。這會對性能產生很大影響,但對分析很有用。
T1206
打印死鎖中的參與 spid
持有的所有鎖。
T1208
打印客戶端提供的主機名和程序名。這有助於確定死鎖中涉及的客戶端,前提是假定客戶端爲每個連接都指定了唯一值。