MSSQL锁定-3.死锁与阻塞(myBased)
MSSQL锁定-1.隔离级别 Isolation level
MSSQL锁定-2.Transaction
MSSQL锁定-3.死锁与阻塞
这一节主要记录死锁的种类,以及死锁和阻塞出现后的检测方法.基本概念就不说了,直接进入主题.这节主要资料来源是<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字段可看出所进行的交易的隔离级别
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