這一節主要記錄死鎖的種類,以及死鎖和阻塞出現後的檢測方法.基本概念就不說了,直接進入主題.這節主要資料來源是<SQL Server 2005 Performance Tuning性能調校>以及一些網上的資源.
--防止死鎖
- 事件查看器或sp_who2系統存儲過程是否有許多連接被封鎖
- master.sys.sysprocesses被鎖定進程的waittime字段的值異常的大
- 監控 SQL Profiler 工具可以檢測TSQL的時間如 SQL:StmtCompleted/SQL:BatchComplete 或者存儲過程事件類別 SP:StmtCompleted/SP:BatchComplete,可觀察SQL語句執行的狀況並通過TextData以及Duration字段判斷哪一句執行時間過長從而導致鎖定
- 儘量不要激活Implicit Transaction免得他長時間持有交易
- 通過Set DeadLock_Priority_Low讓不重要的交易自動放棄
- 使用sp_GetBindToken和sp_BindSession兩個系統存儲過程,讓連接共享鎖定(這個沒使用過只是看了介紹)
--一些例子
- 問題:由於不正確的交易和交易隔離級別所導致
解決方法:找到問題session_id同時檢查sys.dm_exec_requests視圖的status字段爲"running",wait_type非"Null"和sys.dm_exec_sessions視圖的transaction_isolation_level字段可看出所進行的交易的隔離級別防止交易中出現錯誤應該有以下邏輯 if @@trancount > 0 rollback tran or set Xact_Abort On,也可使用 DBCC OpenTran ('DB_NAME')觀察DB中最長交易select der.status,der.wait_type,des.transaction_isolation_level
from sys.dm_exec_requests der,sys.dm_exec_sessions des
</span>where der.session_id = des.session_id - 未檢測到的分佈式死鎖
找到問題session_id同時檢查sys.dm_exec_requests視圖的status字段爲"sleeping",wait_type爲"Null" ,開啓交易字段非0 - 胡亂使用Lock Hint 使用 DBCC TraceOn(8755)或者SQL Server激活參數-T8755來停止鎖定提示功能
--死鎖
死鎖分爲幾類Cycle死鎖,Conversion死鎖,分佈式死鎖和SSIS死鎖,看下邊的測試
- --Cycle死鎖
--1.在Session1運行以下
begin tran
update Sales.Customer set TerritoryID =2 where CustomerID = 1
--2.在Session2運行以下
begin tran
update Sales.Customer set TerritoryID =1 where CustomerID = 2
--3.回到Session1運行以下
update Sales.Customer set TerritoryID =1 where CustomerID = 2
--4.回到Session2運行以下
update Sales.Customer set TerritoryID =2 where CustomerID = 1
--Conversion死鎖
--在Session 1,2同時運行下邊代碼
Set Transaction Isolation Level Repeatable Read
begin tran
select * from Sales.Customer where CustomerID = 2
WaitFor Delay '00:00:03'
update Sales.Customer set TerritoryID =convert(nvarchar,2) where CustomerID = 1
Commit Tran
select * from Sales.Customer where CustomerID = 2
--分佈式死鎖
--SQL Server無法偵測的死鎖
USE Tempdb
CREATE TABLE tbTestTrigSSIS(ID INT IDENTITY(1,1), Done BIT DEFAULT 0)
GO
EXEC sp_configure 'show advanced options',1
RECONFIGURE
--測試完畢後要改回來,免得留下安全漏洞
EXEC sp_configure 'xp_cmdshell',1
RECONFIGURE
GO
create TRIGGER trgForInsert ON tbTestTrigSSIS
FOR INSERT
AS
--先完成交易,再執行其他的工作
COMMIT TRAN
EXEC master.dbo.xp_cmdshell 'dtexec /FILE "E:\SSISDeadLock\Package.dtsx"'
BEGIN TRAN
GO
Insert tbTestTrigSSIS default values
--阻塞與檢測
製造阻塞
--Session 1
begin tran
update Sales.Customer set TerritoryID =2 where CustomerID = 1
--Session 2
select * from Sales.Customer where CustomerID = 1- 查看阻塞進程 master.sys.sysprocesses-----------------------------------------------------
--列出最初鎖住資源,導致一連串其他進程被鎖住的起始源頭
-----------------------------------------------------
IF EXISTS(SELECT * FROM master.sys.sysprocesses WHERE spid
IN (SELECT blocked FROM master.sys.sysprocesses)) --確定有進程被其他的進程鎖住
SELECT
DISTINCT '進程ID' = STR(a.spid, 4)
,'進程ID狀態' = CONVERT(CHAR(10), a.status)
,'登入帳號'=SUBSTRING(SUSER_SNAME(sid),1,30)
,'工作站名稱' = CONVERT(CHAR(10), a.hostname)
,'執行命令的用戶' = CONVERT(CHAR(10), SUSER_NAME(a.uid))
,'是否被鎖住'=CONVERT(char(3),blocked)
,'數據庫名' = CONVERT(CHAR(10), DB_NAME(a.dbid))
,'正在執行的命令' = CONVERT(CHAR(16), a.cmd)
,'登錄名' = a.loginame
,'執行語句' = b.text
,'等待型態' = a.waittype
FROM master..sysprocesses a CROSS APPLY sys.dm_exec_sql_text(a.sql_handle) b
--列出鎖住別人(在別的進程中 blocked字段出現的值),但自己未被鎖住(blocked=0)
WHERE spid IN (SELECT blocked FROM master.sys.sysprocesses)
AND blocked=0
ELSE
SELECT 'No Blocked Session(s)'
--a.status = suspended,a.blocked(阻塞者id)
--DBCC INPUTBUFFER (阻塞者id);--就可以看到語句了或者join-----------------------------------------------------經常出現的是,在sysprocesses視圖中 status是'sleeping',
waittype字段是0x0000,打開事務數open_tran大於0,
一般都是交易已經激活但遲遲沒有結束,就可能是程序沒有管理好交易管理-----------------------------------------------------select * from master.sys.sysprocesseswhere status = 'sleeping' and waittype=0x0000 and open_tran > 0 - 05可以通過sys.dm_tran_locks視圖查看,每一筆記錄都代表已經授予鎖定,注意資源resource和請求request,sys.dm_os_waiting_tasks視圖提供時間統計誰鎖定誰sys.dm_exec_requests視圖以及sys.dm_exec_sql_text(r.sql_handle)函數
3.1 我們可以使用系統監視器(Perfmon)來判斷SQL Server 2005中是否有鎖引起的阻塞。SQLServer:General Statistics對象中被阻進程計數器會顯示被阻塞進程的數目。我們可以從SQLServer:Wait Statistics對象中增加如"鎖等待"等計數器來判斷正在發生的等待類型的數量和持續時間。"系統監視器"計數器只提供總數,因此爲了挖掘更深的內容,我們需要更深入地研究系統。然而,被阻塞進程計數器可以讓我們知道問題的大小和一段時間內阻塞的行爲。
3.2 通過從sys.dm_os_waiting_tasks和sys.dm_tran_locks等的DMV,我們可以得到阻塞的準確詳細的信息。儘管如此,我們還可以用sp_who和sp_who2系統存儲過程來監視進程,關注blk或BlkBy列來找出阻塞者和阻塞進程。但是,sp_who結果中只涉及session報告,不是查詢本身或引起問題的鎖類型。我們可以進一步查看主數據庫進程系統視圖,查找進程是否在事務中的數據;而且我們還可以用已有的DBCC INOUTBUFFER命令行來檢查批處理命令是否被session正確執行,儘管sys.dm_exec_requests DMV由於可以直接過濾而更通用。儘管如此,session級別的細節是有限的,因爲它不會區分並行查詢可能執行的多任務。
注意: 當使用sp_who2存儲過程時,我們可能會看到session或spid自我阻塞。在一個被鎖定的資源上,spid並沒有實際自我鎖定。通常情況下,這發生在並行查詢時,且意味着spid在等待I/O。
使用sys.dm_os_waiting_tasks DMV
相對於sp_who或sp_who2存儲過程,或者主數據庫進程視圖來說,使用sys.dm_os_waiting_tasks DMV(或此DMV的過濾視圖)檢測阻塞問題有明顯優點。
最重要的是,在一個忙碌的系統中,我們不必費力地完成與阻塞無關的任務或session。視圖只顯示正在等待的任務。
同樣,sys.dm_os_waiting_tasks在任務級別返回的信息比在session級別更細緻。如果查詢是並行的,並且它的某個任務阻塞了或被阻塞,sys.dm_os_waiting_tasks會顯示哪個任務實際上與阻塞有關,除了session之外。
阻塞者的信息也會顯示。
另一個明顯優點是,sys.dm_os_waiting_tasks返回等待的持續時間,因此我們可以在視圖中通過過濾來查看等待時間是否足夠長到被關注。
某些情況下,blocking_session_id可能不指向一個存在的session id或spid。如SQL Server 2005在線教程中關於sys.dm_os_waiting_tasks的討論所說,有時候列可能爲空,因爲沒有阻塞session,或者它不能被定義。當我們在多用戶系統中檢查鎖阻塞時,這並不常見。但在某些條件下,SQL Server報告blocking_session_id爲負數。session id爲負數時,有3個可能的代碼:
-2 = 被阻塞資源屬於孤立分佈式事務。
-3 = 被阻塞資源屬於遞延恢復事務。
-4 = 對於鎖存器等待,內鎖存器狀態轉換阻止了session id的識別。
因此我們需要注意session id的值是否在blocking_session_id列出現。
通過設定持續時間閾值,我們可以過濾出不需要的等待,而關注於可能長時間阻塞的等待。例如,下面的查詢只顯示出發生了5秒以上的等待:
SELECT
WT.session_id AS waiting_session_id,
WT.waiting_task_address,
WT.wait_duration_ms,
WT.wait_type,
WT.blocking_session_id,
WT.resource_description
FROM sys.dm_os_waiting_tasks AS WT
WHERE WT.wait_duration_ms > 5000;
3.3 SQL Trace/Profiler中的Lock:Escalation事件類。當升級發生時,該事件被觸發。但一個升級會有多個觸發,所以將它們綁定在一起很重要。
確保選擇Lock:Escalation事件類的默認列,這些列提供基本信息。但我們添加以下列可能也很有用:TransactionID、DatabaseID、DatabaseName和ObjectID。因爲可能看到trace中一個升級事件的多行,可以使用TransactionID將它們綁定在一起,特定對象(即表)可以使用ObjectID。
通過監視表鎖的數目或者和它們的持續時間,可以檢測到正在發生的升級。如果可以估計應用系統很少需要(或者曾經需要)表上的共享鎖或獨佔鎖,就可以推斷無論什麼時候我們看到這樣的鎖,它都由鎖升級產生。可以通過sys.dm_tran_locks DMV在給定的時間點探測表鎖。下面的查詢顯示了一個實例:
SELECT request_session_id, resource_type, DB_NAME(resource_database_id) AS DatabaseName,
OBJECT_NAME(resource_associated_entity_id) AS TableName, request_mode, request_type, request_status
FROM sys.dm_tran_locks AS TL JOIN sys.all_objects AS AO ON TL.resource_associated_entity_id = AO.object_id
WHERE request_type = 'LOCK' AND request_status = 'GRANT' AND request_mode IN ('X','S') AND AO.type = 'U'
AND resource_type = 'OBJECT' AND TL.resource_database_id = DB_ID();
上面用來查找表鎖的查詢引用了sys.all_objects的目錄視圖,所以返回信息的範圍限制在查詢運行的數據庫上。由於sys.dm_tran_locks沒有返回鎖定對象更詳細的信息,就沒有辦法得知這個對象是否是表。這樣一來,就必須加入返回那些信息的數據庫的一些東西,而在這種情況下,sys.all_objects包含這些信息,而且OBJECT_NAME()函數可以返回表的名稱。(實例見第1章"性能故障檢修方法"。)但是,它們都只返回當前數據庫的信息。因此,查詢過濾器的最後一個條件限制了當前數據庫中那些資源的返回行。
另一種策略是使用sp_lock系統存儲過程,它返回鎖類型,從而可以查看錶鎖。不幸的是,爲了過濾sp_lock,必須抓取臨時數據,然後查詢它並在一個WHERE子句中過濾。
可以從sp_lock存儲過程中提取key並執行它,但是它只適合於查詢sys.dm_tran_locks DMV並對其過濾。
- select t1.resource_type as [資源鎖定類型]
,db_name(resource_database_id) as [數據庫名]
,t1.resource_associated_entity_id as [鎖定的對象]
,t1.request_mode as [等待者需求的鎖定類型]
,t1.request_session_id as [等待者sid]
,t2.wait_duration_ms as [等待時間]
,(select text from sys.dm_exec_requests as r
cross apply sys.dm_exec_sql_text(r.sql_handle)
where r.session_id = t1.request_session_id) as [等待者要執行的批次]
,(select substring(qt.text,r.statement_start_offset/2+1,
(case when r.statement_end_offset = -1
then datalength(qt.text)
else r.statement_end_offset end - r.statement_start_offset)/2+1)
from sys.dm_exec_requests as r
cross apply sys.dm_exec_sql_text(r.sql_handle) as qt
where r.session_id = t1.request_session_id) as [等待者正要執行的語法]
,t2.blocking_session_id as [鎖定者sid]
,(select text from sys.sysprocesses as p
cross apply sys.dm_exec_sql_text(p.sql_handle)
where p.spid = t2.blocking_session_id) as [鎖定者的語法]
from
sys.dm_tran_locks as t1,
sys.dm_os_waiting_tasks as t2
where
t1.lock_owner_address = t2.resource_address - 研究下sp_who2和sp_lock
- --Session 1
use adventureworks
go
BEGIN TRANSACTION --啓動交易,執行修改陳述式
UPDATE HumanResources.Employee SET ManagerID=4 WHERE EmployeeID=2
--Session 2
use adventureworks
go
SELECT * FROM HumanResources.Employee WHERE EmployeeID=2
GO
exec sp_lock 55
select db_name(5) 'db',object_name(869578136) 'object',
(select name from sys.indexes where object_id=869578136 and index_id=5) 'index_name'
--index_id是根據sp_lock的IndId字段
DBCC TraceOn(3604)
DBCC Page (5,1,1731,3) --5是DBID,1是集羣索引,大於1是非集羣,1731是根據sp_lock的Resource字段USE AdvantureWorks
GO
--建立暫存 sp_lock 輸出的數據表
IF EXISTS(SELECT * FROM tempdb..sysobjects WHERE Name LIKE '#Temp%' AND Type='u' )
DROP TABLE #Temp
CREATE TABLE #Temp
(spid int,dbid int,ObjId int,IndId int,Type varchar(3),Resouse varchar(20),Mode varchar(5),Status varchar(5))
BEGIN TRAN
UPDATE Customers SET CompanyName='abcxyz' WHERE CustomerID='alfki'
INSERT #Temp EXEC sp_lock @@spid
COMMIT TRAN
--只看與我們自定數據庫相關的資源,所以 dbid > 5
SELECT spid,數據庫=db_name(dbid),物件=object_name(ObjId),
索引=(SELECT name FROM sysindexes WHERE id=ObjId AND indid=t.IndId),
Type,Resouse,Mode,Status FROM #Temp t WHERE dbid>5
ORDER BY dbid,objid,indid
--或以 SQL Server 2005 的 sys.indexes 查詢相關數據
SELECT spid,數據庫=db_name(dbid),物件=object_name(ObjId),
索引=(SELECT name FROM sys.indexes WHERE object_id=ObjId AND index_id=t.IndId),
Type,Resouse,Mode,Status FROM #Temp t WHERE dbid>5
ORDER BY dbid,objid,indid