[SQL]建立Index來避免DeadLock

  • 15406
  • 0
  • SQL
  • 2014-05-31

大家都知道Scan去找資料是不好的,但您知道這樣的SQL在執行中,會拿了多少的Lock嗎?
我們可以從SQL Profiler中的Lock:Acquired及Lock:Released來觀察哦!

最近在論談上看到「两个事务 更新一张堆表 遇到奇怪的死锁问题」。

這讓我想到之前在TechDay上到強哥說的,你認為SQL Update時,一定是Row Lock嗎?

當某個Table沒有建立任何的Index,要更新資料就是要一筆筆的找,解法就是建立index把資料打散!

強哥的測試Script如下(使用TSQL2012),


--SESSION I

USE TSQL2012
GO
IF OBJECT_ID('employees') IS NOT NULL
    DROP TABLE employees
GO

SELECT  *
INTO    employees
FROM    [HR].[Employees]
GO

SELECT  *
FROM    [dbo].[employees]

BEGIN TRANSACTION 
UPDATE  [dbo].[employees]
SET     [firstname] = [firstname] + '@'
WHERE   empid = 1

 
SELECT  *
FROM    [dbo].[employees]
WHERE   empid = 1

--ROLLBACK

--SESSION 2
SELECT  *
FROM    [dbo].[employees]
WHERE   empid = 2
--WHERE lastname='Funk'

--solution
DROP INDEX IDX_SPLIT ON [dbo].[employees]

CREATE CLUSTERED INDEX IDX_SPLIT ON [dbo].[employees]
([empid] DESC,[lastname] ASC  )
 GO

 

那這個問題的狀況也類似,Script如下,


USE tempdb
GO

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[table1](
 [A] [nvarchar](10) NULL,
 [B] [nvarchar](10) NOT NULL,
 [C] [nvarchar](10) NULL
) ON [PRIMARY]
GO
INSERT [dbo].[table1] ([A], [B], [C]) VALUES (N'aa1', N'b1', N'11')
INSERT [dbo].[table1] ([A], [B], [C]) VALUES (N'aa2', N'b3', N'11')
INSERT [dbo].[table1] ([A], [B], [C]) VALUES (N'aa3', N'b4', N'11')
INSERT [dbo].[table1] ([A], [B], [C]) VALUES (N'aa3', N'b5', N'11')
INSERT [dbo].[table1] ([A], [B], [C]) VALUES (N'aa3', N'b2', N'11')
INSERT [dbo].[table1] ([A], [B], [C]) VALUES (N'aa3', N'b6', N'11')
INSERT [dbo].[table1] ([A], [B], [C]) VALUES (N'aa3', N'b7', N'11')
INSERT [dbo].[table1] ([A], [B], [C]) VALUES (N'aa3', N'b8', N'11')
INSERT [dbo].[table1] ([A], [B], [C]) VALUES (N'aa1', N'b9', N'11')

 

開2個查詢視窗,先執行第1個查詢,再切到第2個查詢執行,


--查詢1
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
BEGIN TRAN
UPDATE  table1
SET     A = 'aa1'
WHERE   B = 'b3'
EXEC sp_lock @@spid
 
WAITFOR  DELAY '00:00:10'

UPDATE  table1
SET     A = 'aa2'
WHERE   B = 'b8'
EXEC sp_lock @@spid
WAITFOR  DELAY '00:00:10'
COMMIT TRAN

--查詢2
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
BEGIN TRAN
UPDATE  table1
SET     A = 'aa3'
WHERE   B = 'b1'
 
EXEC sp_lock @@spid
COMMIT TRAN

 

等一下子後,查詢2就會被犧牲掉,如下,

Msg 1205, Level 13, State 45, Line 3
Transaction (Process ID 117) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.

Msg 1205, Level 13, State 45, Line 3
交易 (處理序識別碼 117) 在 鎖定 資源上被另一個處理序鎖死並已被選擇作為死結的犧牲者。請重新執行該交易。

 

DeadLock的圖如下,

image

 

sp_lock的資訊如下,

image

 

Update時,因為沒有任何的index,所以是用Scan的方式,如下,

image

 

所以,它會一筆筆地找,先取得Update Lock,不符合就Rease Update Lock,然後再取下一筆,有符合的,就取得  Exclusive Lock 。

而第2個查詢,會等待是因為要取得 Update Lock ,而該筆資料被查詢1取得了Exclusive Lock了!

image

 

So...這種狀況就是建立Index讓它可以馬上找到,而不需用Scan的方式去找,而造成Lock的等待,


CREATE NONCLUSTERED INDEX nidx_table1_B
ON table1(B);

而建立了index後,可以發現,不會逐筆掃,而會直接針對該筆資料進行 Update 及 Exclusive Lock 然後修改完資料後,Release Lock! 如下,

image

 

 

參考資料

Using a Clustered Index to Solve a SQL Server Deadlock Issue

两个事务 更新一张堆表 遇到奇怪的死锁问题

Hi, 

亂馬客Blog已移到了 「亂馬客​ : Re:從零開始的軟體開發生活

請大家繼續支持 ^_^