雖然不能完全避免死鎖,但可以使死鎖的數量減至最少。將死鎖減至最少可以增加事務的吞吐量并減少系統開銷,因為只有很少的事務:
- 回滾,而回滾會取消事務執行的所有工作。
- 由于死鎖時回滾而由應用程序重新提交。
下列方法有助于最大限度地降低死鎖:
- 按同一順序訪問對象。
- 避免事務中的用戶交互。
- 保持事務簡短并在一個批處理中。
- 使用低隔離級別。
- 使用綁定連接。
按同一順序訪問對象
如果所有并發事務按同一順序訪問對象,則發生死鎖的可能性會降低。例如,如果兩個并發事務獲得 Supplier表上的鎖,然后獲得 Part表上的鎖,則在其中一個事務完成之前,另一個事務被阻塞在 Supplier表上。第一個事務提交或回滾后,第二個事務繼續進行。不發生死鎖。將存儲過程用于所有的數據修改可以標準化訪問對象的順序。
避免事務中的用戶交互
避
免編寫包含用戶交互的事務,因為運行沒有用戶交互的批處理的速度要遠遠快于用戶手動響應查詢的速度,例如答復應用程序請求參數的提示。例如,如果事務正在
等待用戶輸入,而用戶去吃午餐了或者甚至回家過周末了,則用戶將此事務掛起使之不能完成。這樣將降低系統的吞吐量,因為事務持有的任何鎖只有在事務提交或
回滾時才會釋放。即使不出現死鎖的情況,訪問同一資源的其它事務也會被阻塞,等待該事務完成。
保持事務簡短并在一個批處理中
在同一數據庫中并發執行多個需要長時間運行的事務時通常發生死鎖。事務運行時間越長,其持有排它鎖或更新鎖的時間也就越長,從而堵塞了其它活動并可能導致死鎖。
保持事務在一個批處理中,可以最小化事務的網絡通信往返量,減少完成事務可能的延遲并釋放鎖。
使用低隔離級別
確定事務是否能在更低的隔離級別上運行。執行提交讀允許事務讀取另一個事務已讀取(未修改)的數據,而不必等待第一個事務完成。使用較低的隔離級別(例如提交讀)而不使用較高的隔離級別(例如可串行讀)可以縮短持有共享鎖的時間,從而降低了鎖定爭奪。
使用綁定連接
使用綁定連接使同一應用程序所打開的兩個或多個連接可以相互合作。次級連接所獲得的任何鎖可以象由主連接獲得的鎖那樣持有,反之亦然,因此不會相互阻塞
檢測死鎖
如果發生死鎖了,我們怎么去檢測具體發生死鎖的是哪條SQL語句或存儲過程?
這時我們可以使用以下存儲過程來檢測,就可以查出引起死鎖的進程和SQL語句。SQL Server自帶的系統存儲過程sp_who和sp_lock也可以用來查找阻塞和死鎖, 但沒有這里介紹的方法好用。
use master
go
createprocedure sp_who_lock
as
begin
declare@spidint,@blint,
@intTransactionCountOnEntryint,
@intRowcountint,
@intCountPropertiesint,
@intCounterint

createtable #tmp_lock_who (
id intidentity(1,1),
spid smallint,
bl smallint)
IF@@ERROR<>0RETURN@@ERROR
insertinto #tmp_lock_who(spid,bl) select0 ,blocked
from (select*from sysprocesses where blocked>0 ) a
wherenotexists(select*from (select*from sysprocesses where blocked>0 ) b
where a.blocked=spid)
unionselect spid,blocked from sysprocesses where blocked>0

IF@@ERROR<>0RETURN@@ERROR
-- 找到臨時表的記錄數
select@intCountProperties=Count(*),@intCounter=1
from #tmp_lock_who
IF@@ERROR<>0RETURN@@ERROR
if@intCountProperties=0
select'現在沒有阻塞和死鎖信息'as message

-- 循環開始
while@intCounter<=@intCountProperties
begin
-- 取第一條記錄
select@spid= spid,@bl= bl
from #tmp_lock_who where Id =@intCounter
begin
if@spid=0
select'引起數據庫死鎖的是: '+CAST(@blASVARCHAR(10)) +'進程號,其執行的SQL語法如下'
else
select'進程號SPID:'+CAST(@spidASVARCHAR(10))+'被'+'進程號SPID:'+CAST(@blASVARCHAR(10)) +'阻塞,其當前進程執行的SQL語法如下'
DBCC INPUTBUFFER (@bl )
end

-- 循環指針下移
set@intCounter=@intCounter+1
end

droptable #tmp_lock_who

return0
end

殺死鎖和進程
如何去手動的殺死進程和鎖?最簡單的辦法,重新啟動服務。但是這里要介紹一個存儲過程,通過顯式的調用,可以殺死進程和鎖。
use master
go

ifexists (select*from dbo.sysobjects where id =object_id(N'[dbo].[p_killspid]') andOBJECTPROPERTY(id, N'IsProcedure') =1)
dropprocedure[dbo].[p_killspid]
GO

createproc p_killspid
@dbnamevarchar(200) --要關閉進程的數據庫名
as
declare@sqlnvarchar(500)
declare@spidnvarchar(20)

declare #tb cursorfor
select spid=cast(spid asvarchar(20)) from master..sysprocesses where dbid=db_id(@dbname)
open #tb
fetchnextfrom #tb into@spid
while@@fetch_status=0
begin
exec('kill '+@spid)
fetchnextfrom #tb into@spid
end
close #tb
deallocate #tb
go

--用法
exec p_killspid 'newdbpy'
