您的位置:首页 > 数据库 > SQL

SQL Server 监控统计阻塞脚本信息

2016-03-21 23:57 721 查看
数据库产生阻塞(Blocking)的本质原因:SQL语句连续持有锁的时间过长,数目过多,粒度过大。阻塞是事务隔离带来的副作用,它是不可避免的,而且是一个数据库系统常见的现象。但是阻塞的时间和出现频率要控制在一定的范围内,阻塞持续的时间过长或阻塞出现过多(过于频繁),就会对数据库性能产生严重的影响。

很多时候,DBA需要知道数据库在出现性能问题时,有没有发生阻塞?什么时候开始的?发生在那个数据库上?阻塞发生在那些SQL语句之间?阻塞的时间有多长?阻塞发生的频率?阻塞有关的连接是从那些客户端应用发送来的?.......

如果我们能够知道这些具体信息,我们就能迅速定位问题,分析阻塞产生的原因,从而找出出现性能问题的根本原因,并根据具体原因给出相应的解决方案(索引调整、优化SQL语句等)。

查看阻塞的方法比较多,我在这篇博客MSSQL日常维护管理常用脚本(二)里面提到查看阻塞的一些方法:

方法1:查看那个引起阻塞,查看blk不为0的记录,如果存在阻塞进程,则是该阻塞进程的会话ID。否则该列为零。

EXECsp_whoactive

方法2:查看那个引起阻塞,查看字段BlkBy,这个能够得到比sp_who更多的信息。

EXECsp_who2active

方法3:sp_lock系统存储过程,报告有关锁的信息,但是不方便定位问题

方法4:sp_who_lock存储过程

方法5:右键服务器-选择“活动和监视器”,查看进程选项。注意“任务状态”字段。

方法6:右键服务名称-选择报表-标准报表-活动-所有正在阻塞的事务。

但是上面方法,例如像sp_who、sp_who2,sp_who_lock等,都有或多或少的缺点:例如不能查看阻塞和被阻塞的SQL语句。不能从查看一段时间内阻塞发生的情况等;没有显示阻塞的时间.......我们要实现下面功能:

1:查看那个会话阻塞了那个会话

2:阻塞会话和被阻塞会话正在执行的SQL语句

3:被阻塞了多长时间

4:像客户端IP、Proagram_Name之类信息

5:阻塞发生的时间点

6:阻塞发生的频率

7:如果需要,应该通知相关开发人员,DBA不能啥事情都包揽是吧,那不还得累死,总得让开发人员员参与进来优化(有些问题就该他们解决),多了解一些系统运行的具体情况,有利于他们认识问题、解决问题。

8:需要的时候开启这项功能,不需要关闭这项功能

于是为了满足上述功能,有了下面SQL语句

SELECTwt.blocking_session_idASBlockingSessesionId


,sp.program_nameASProgramName


,COALESCE(sp.LOGINAME,sp.nt_username)ASHostName


,ec1.client_net_addressASClientIpAddress


,db.nameASDatabaseName


,wt.wait_typeASWaitType


,ec1.connect_timeASBlockingStartTime


,wt.WAIT_DURATION_MS/1000ASWaitDuration


,ec1.session_idASBlockedSessionId


,h1.TEXTASBlockedSQLText


,h2.TEXTASBlockingSQLText


FROMsys.dm_tran_locksAStl


INNERJOINsys.databasesdb


ONdb.database_id=tl.resource_database_id


INNERJOINsys.dm_os_waiting_tasksASwt


ONtl.lock_owner_address=wt.resource_address


INNERJOINsys.dm_exec_connectionsec1


ONec1.session_id=tl.request_session_id


INNERJOINsys.dm_exec_connectionsec2


ONec2.session_id=wt.blocking_session_id


LEFTOUTERJOINmaster.dbo.sysprocessessp


ONSP.spid=wt.blocking_session_id


CROSSAPPLYsys.dm_exec_sql_text(ec1.most_recent_sql_handle)ASh1


CROSSAPPLYsys.dm_exec_sql_text(ec2.most_recent_sql_handle)ASh2

测试:

打开窗口1:


BEGINTRANSACTION

updateTb_Sys_UsersetCommID=0

--COMMITTRANSACTION;

打开窗口2:

select*fromtb_sys_user

现在上面SQL已经基本实现了查看阻塞具体信息的功能,但是现在又有几个问题:

1:上面SQL脚本只适合已经出现阻塞情况下查看阻塞信息,如果没有出现阻塞情况,我总不能傻傻的一直在哪里点击执行吧,因为阻塞这种情况有可能在那段时间都不会出现,只会在特定的时间段出现。

2:我想了解一段时间内数据库出现的阻塞情况,那么需要将阻塞信息保留下来。

3:有时候忙不过来,我想将这些具体阻塞信息发送给相关开发人员,让他们了解具体情况。

于是我想通过一个存储过程来实现这方面功能,通过设置参数@OutType,默认为输出阻塞会话信息,当参数为"Table"时,将阻塞信息写入数据库表,如果参数为"Email"表示将阻塞信息通过邮件发送开发人员。

USE[YourSQLDba]


GO


IFNOTEXISTS(SELECT*FROMsys.objectsWHEREobject_id=OBJECT_ID(N'[Maint].[BlockingSQLHistory]')ANDtype='U')


BEGIN


CREATETABLEMaint.BlockingSQLHistory


(


RecordTimeDATETIME,


DatabaseNameSYSNAME,


BlockingSessesionIdSMALLINT,


ProgramNameNCHAR(128),


UserNameNCHAR(256),


ClientIpAddressVARCHAR(48),


WaitTypeNCHAR(60),


BlockingStartTimeDATETIME,


WaitDurationBIGINT,


BlockedSessionIdINT,


BlockedSQLTextNVARCHAR(MAX),


BlockingSQLTextNVARCHAR(MAX),


CONSTRAINTPK_BlockingSQLHistoryPRIMARYKEY(RecordTime)


)




END


GO

/******************************************************************************************************************

Parameters:参数说明

********************************************************************************************************************

@OutType:默认为输出阻塞会话信息,"Table","Email"分别表示将阻塞信息写入表或邮件发送

@EmailSubject:邮件主题.默认为SqlBlockingAlert,一般指定,例如“ServerNameSqlBlockingAlert"

@ProfileName:@profile_name默认值为YourSQLDba_EmailProfile

@RecipientsLst:收件人列表

********************************************************************************************************************

ModifiedDateModifiedUserVersionModifiedReason

********************************************************************************************************************

2014-04-23KerryV01.00.00新建存储过程[Maint].[sp_who_blocking]


*******************************************************************************************************************/

--==================================================================================================================

CREATEPROCEDURE[Maint].[sp_who_blocking]

(

@OutType

VARCHAR(8)='Default',

@EmailSubject

VARCHAR(120)='SqlBlockingAlert',

@ProfileName

sysname='YourSQLDba_EmailProfile',

@RecipientsLst

VARCHAR(MAX)=NULL

)

AS

BEGIN


SETNOCOUNTON;


DECLARE@HtmlContentNVARCHAR(MAX);


IF@OutTypeNOTIN('Default','Table','Email')

BEGIN

PRINT'Theparameter@OutTypeisnotcorrect,pleasecheckit';


return;

END


IF@OutType='Default'

BEGIN


SELECTdb.nameASDatabaseName

,wt.blocking_session_idASBlockingSessesionId

,sp.program_nameASProgramName

,COALESCE(sp.LOGINAME,sp.nt_username)ASUserName

,ec1.client_net_addressASClientIpAddress

,wt.wait_typeASWaitType

,ec1.connect_timeASBlockingStartTime

,wt.WAIT_DURATION_MS/1000ASWaitDuration

,ec1.session_idASBlockedSessionId

,h1.TEXTASBlockedSQLText

,h2.TEXTASBlockingSQLText

FROMsys.dm_tran_locksAStl

INNERJOINsys.databasesdb

ONdb.database_id=tl.resource_database_id

INNERJOINsys.dm_os_waiting_tasksASwt

ONtl.lock_owner_address=wt.resource_address

INNERJOINsys.dm_exec_connectionsec1

ONec1.session_id=tl.request_session_id

INNERJOINsys.dm_exec_connectionsec2

ONec2.session_id=wt.blocking_session_id

LEFTOUTERJOINmaster.dbo.sysprocessessp

ONSP.spid=wt.blocking_session_id

CROSSAPPLYsys.dm_exec_sql_text(ec1.most_recent_sql_handle)ASh1

CROSSAPPLYsys.dm_exec_sql_text(ec2.most_recent_sql_handle)ASh2;

END

ELSEIF@OutType='Table'

BEGIN


INSERTINTO[Maint].[BlockingSQLHistory]

SELECTGETDATE()ASRecordTime

,db.nameASDatabaseName

,wt.blocking_session_idASBlockingSessesionId

,sp.program_nameASProgramName

,COALESCE(sp.LOGINAME,sp.nt_username)ASUserName

,ec1.client_net_addressASClientIpAddress

,wt.wait_typeASWaitType

,ec1.connect_timeASBlockingStartTime

,wt.WAIT_DURATION_MS/1000ASWaitDuration

,ec1.session_idASBlockedSessionId

,h1.TEXTASBlockedSQLText

,h2.TEXTASBlockingSQLText

FROMsys.dm_tran_locksAStl

INNERJOINsys.databasesdb

ONdb.database_id=tl.resource_database_id

INNERJOINsys.dm_os_waiting_tasksASwt

ONtl.lock_owner_address=wt.resource_address

INNERJOINsys.dm_exec_connectionsec1

ONec1.session_id=tl.request_session_id

INNERJOINsys.dm_exec_connectionsec2

ONec2.session_id=wt.blocking_session_id

LEFTOUTERJOINmaster.dbo.sysprocessessp

ONSP.spid=wt.blocking_session_id

CROSSAPPLYsys.dm_exec_sql_text(ec1.most_recent_sql_handle)ASh1

CROSSAPPLYsys.dm_exec_sql_text(ec2.most_recent_sql_handle)ASh2;

END

ELSEIF@OutType='Email'

BEGIN


SET@HtmlContent=

N'<head>'

+N'<styletype="text/css">h2,body{font-family:Arial,verdana;}table{font-size:11px;border-collapse:collapse;}td{border:1pxsolidblack;padding:3px;}th{background-color:#99CCFF;}</style>'

+N'<tableborder="1">'

+N'<tr>

<th>DatabaseName</th>

<th>BlockingSessesionId</th>

<th>ProgramName</th>

<th>UserName</th>

<th>ClientIpAddress</th>

<th>WaitType</th>

<th>BlockingStartTime</th>

<th>WaitDuration</th>

<th>BlockedSessionId</th>

<th>BlockedSQLText</th>

<th>BlockingSQLText</th>

</tr>'+

CAST(

(SELECTdb.nameASTD,''

,wt.blocking_session_idASTD,''

,sp.program_nameASTD,''

,COALESCE(sp.LOGINAME,sp.nt_username)ASTD,''

,ec1.client_net_addressASTD,''

,wt.wait_typeASTD,''

,ec1.connect_timeASTD,''

,wt.WAIT_DURATION_MS/1000ASTD,''

,ec1.session_idASTD,''

,h1.TEXTASTD,''

,h2.TEXTASTD,''


FROMsys.dm_tran_locksAStl

INNERJOINsys.databasesdb

ONdb.database_id=tl.resource_database_id

INNERJOINsys.dm_os_waiting_tasksASwt

ONtl.lock_owner_address=wt.resource_address

INNERJOINsys.dm_exec_connectionsec1

ONec1.session_id=tl.request_session_id

INNERJOINsys.dm_exec_connectionsec2

ONec2.session_id=wt.blocking_session_id

LEFTOUTERJOINmaster.dbo.sysprocessessp

ONSP.spid=wt.blocking_session_id

CROSSAPPLYsys.dm_exec_sql_text(ec1.most_recent_sql_handle)ASh1

CROSSAPPLYsys.dm_exec_sql_text(ec2.most_recent_sql_handle)ASh2


FORXMLPATH('tr'),TYPE

)ASNVARCHAR(MAX))+

N'</table>'



IF@HtmlContentISNOTNULL


BEGIN


EXECmsdb.dbo.sp_send_dbmail

@profile_name=@ProfileName,

@recipients=@RecipientsLst,

@subject=@EmailSubject,

@body=@HtmlContent,

@body_format='HTML';


END

END


END

GO

最后在数据库新建一个作业,调用该存储过程,然后在某段时间启用作业监控数据库的阻塞情况,作业的执行频率是个比较难以定夺的头痛问题,具体要根据系统情况来决定,我习惯2分钟执行一次。

最后,这个脚本还有一个问题,如果阻塞或被阻塞的SQL语句是某个存储过程里面的一段脚本,显示的SQL是整个存储过程,而不是正在执行的SQL语句,目前还没有想到好的方法解决这个问题。我目前手工去查看阻塞情况,如果非要查看存储过程里面被阻塞的正在执行的SQL,一般结合下面SQL语句查看(输入阻塞或被阻塞会话ID替代@sessionid)

SELECT[Spid]=er.session_id


,[ecid]


,[Database]=DB_NAME(sp.dbid)


,[Start_Time]


,[SessionRunTime]=datediff(SECOND,start_time,getdate())


,[SqlRunTime]=RIGHT(convert(varchar,


dateadd(ms,datediff(ms,sp.last_batch,getdate()),'1900-01-01'),


121),12)


,[HostName]


,[Users]=COALESCE(sp.LOGINAME,sp.nt_username)


,[Status]=er.status


,[WaitType]=er.wait_type


,[Waitime]=er.wait_time/1000


,[IndividualQuery]=SUBSTRING(qt.text,er.statement_start_offset/2,


(CASEWHENer.statement_end_offset=-1


THENLEN(CONVERT(NVARCHAR(MAX),qt.text))


*2


ELSEer.statement_end_offset


END-er.statement_start_offset)/2)


,[ParentQuery]=qt.text


,[PROGRAM_NAME]=program_name


FROMsys.dm_exec_requestser


INNERJOINsys.sysprocessesspONer.session_id=sp.spid


CROSSAPPLYsys.dm_exec_sql_text(er.sql_handle)ASqt


WHEREsession_Id=@sessionid;

注:此文为转载,感谢原文作者!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: