在今天的文章里我想演示下SQL Server里在表上丟失索引如何引起死鎖(deadlock)的。為了準備測試場景,下列代碼會創建2個表,然后2個表都插入4條記錄。
1 -- Create a table without any indexes 2 CREATE TABLE Table1 3 ( 4 Column1 INT, 5 Column2 INT 6 ) 7 GO 8 9 -- Insert a few record10 INSERT INTO Table1 VALUES (1, 1)11 INSERT INTO Table1 VALUES (2, 2)12 INSERT INTO Table1 VALUES (3, 3)13 INSERT INTO Table1 VALUES (4, 4)14 GO15 16 -- Create a table without any indexes17 CREATE TABLE Table218 (19 Column1 INT,20 Column2 INT21 )22 GO23 24 -- Insert a few record25 INSERT INTO Table2 VALUES (1, 1)26 INSERT INTO Table2 VALUES (2, 2)27 INSERT INTO Table2 VALUES (3, 3)28 INSERT INTO Table2 VALUES (4, 4)29 GO
在我向你重現死鎖前,先看下列的代碼,它是個簡單的UPDATE語句,在第1個表里更新一個指定行。
1 -- Acquires an Exclusive Lock on the row2 UPDATE Table1 SET Column1 = 3 WHERE Column2 = 1
因為在Column2上沒有索引定義,對于我們的UPDATE語句,查詢優化器在執行計劃里必須選擇表掃描(Table Scan)運算符來查找符合的記錄:
這就是說我們必須掃描整個堆表來找我們想更新的行。在那個情況下,SQL Server用排它鎖(Exclusive Lock)鎖定表里的第1行。當你在不同的會話執行一個SELECT語句,引用另一個堆表里“將發生”的行,表掃描(Table Scan)運算符會阻塞,因為首先你必須讀取所有堆表里“已發生”的行,即獲取你查詢里邏輯請求的行。
-- This query now requests a Shared Lock, but get's blocked, because the other session/transaction has an Exclusive Lock on one row, that is currently updatedSELECT Column1 FROM Table1WHERE Column2 = 4
表掃描(Table Scan)默認意味這你必須掃描整個表,因此你必須在每條記錄上獲得共享鎖(Shared Lock)——即使在你邏輯上不請求的記錄上。如果你用不同的順序,在不同的會話里訪問2個表,當你從同個表嘗試讀寫時,這個情況會導致死鎖情形。下面代碼顯示來自第1個查詢的事務:
1 BEGIN TRANSACTION 2 3 -- Acquires an Exclusive Lock on the row 4 UPDATE Table1 SET Column1 = 3 WHERE Column2 = 1 5 6 -- Execute the query from Session 2... 7 -- This query acquires an Exclusive Lock on one row from Table2... 8 9 -- This query now requests a Shared Lock, but get's blocked, because the other session/transaction has an Exclusive Lock on one row, that is currently updated10 SELECT Column1 FROM Table211 WHERE Column2 = 312 13 ROLLBACK TRANSACTION14 GO
下面顯示來自第2個事務的代碼:
1 BEGIN TRANSACTION 2 3 -- Acquires an Exclusive Lock on the row 4 UPDATE Table2 SET Column1 = 5 WHERE Column2 = 2 5 6 -- Continue with the query from Session 2... 7 -- This query now requests a Shared Lock, but get's blocked, because the other session/transaction has an Exclusive Lock on one row, that is currently updated 8 9 -- This query now requests a Shared Lock, but get's blocked, because the other session/transaction has an Exclusive Lock on one row, that is currently updated10 SELECT Column1 FROM Table111 WHERE Column2 = 412 13 ROLLBACK TRANSACTION14 GO
從2個事務可以看到,2個表在不同的順序里被訪問。如果時機合適,在同個時間運行這2個事務會導致死鎖(deadlock)情形。假設下列的執行順序:
下圖演示了這個死鎖情形:
現在2個事務相互阻塞,因此在SQL Server里你引起了死鎖。在那個情況下死鎖監控器(Deadlock Monitor)后臺進程踢入,進行最“便宜”的事務的回滾(基于事務需要寫入事務日志的字節數)。
你可以在2個表里通過為Column2提供一個索引來輕松解決這個死鎖。在那個情況下SQL Server可以進行符合列的查找(Seek)運算符操作,因此當你執行SELECT語句時,可以跳過已經在索引葉子層的鎖定行:
1 CREATE NONCLUSTERED INDEX idx_Column2 ON Table1(Column2)2 CREATE NONCLUSTERED INDEX idx_Column2 ON Table2(Column2)3 GO
下圖演示了現在的死鎖情形是怎樣的:
使用查找操作你可以跳過索引葉子層的鎖定行,你可以避免我們已經討論過的死鎖。因此當你在你的數據庫看到死鎖情形時,仔細看下你的索引戰略(設計),這非常重要!在SQL Server里,索引一直是一個很重要的東西——始終記住這個!
感謝關注!
新聞熱點
疑難解答