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

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

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

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

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

    EXEC sp_who active

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

    EXEC sp_who2 active

方法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 语句

SELECT wt.blocking_session_id                  AS BlockingSessesionId

      ,sp.program_name                         AS ProgramName

      ,COALESCE(sp.LOGINAME, sp.nt_username)   AS HostName    

      ,ec1.client_net_address                  AS ClientIpAddress

      ,db.name                                 AS DatabaseName        

      ,wt.wait_type                            AS WaitType                    

      ,ec1.connect_time                        AS BlockingStartTime

      ,wt.WAIT_DURATION_MS/1000                AS WaitDuration

      ,ec1.session_id                          AS BlockedSessionId

      ,h1.TEXT                                 AS BlockedSQLText

      ,h2.TEXT                                 AS BlockingSQLText

FROM sys.dm_tran_locks AS tl

INNER JOIN sys.databases db

  ON db.database_id = tl.resource_database_id

INNER JOIN sys.dm_os_waiting_tasks AS wt

  ON tl.lock_owner_address = wt.resource_address

INNER JOIN sys.dm_exec_connections ec1

  ON ec1.session_id = tl.request_session_id

INNER JOIN sys.dm_exec_connections ec2

  ON ec2.session_id = wt.blocking_session_id

LEFT OUTER JOIN master.dbo.sysprocesses sp

  ON SP.spid = wt.blocking_session_id

CROSS APPLY sys.dm_exec_sql_text(ec1.most_recent_sql_handle) AS h1

CROSS APPLY sys.dm_exec_sql_text(ec2.most_recent_sql_handle) AS h2

我们做一个测试例子来验证一下

1:打开第一会话窗口1,执行下面语句

USE DBMonitor;

 

GO

 

BEGIN TRANSACTION

 

SELECT * FROM dbo.TEST(TABLOCKX);

 

--COMMIT TRANSACTION;

 

2:打开第二个会话窗口2,执行下面语句

USE DBMonitor;

GO

SELECT * FROM dbo.TEST

3:打开第三个会话窗口3,执行下面语句

SELECT wt.blocking_session_id                  AS BlockingSessesionId

      ,sp.program_name                         AS ProgramName

      ,COALESCE(sp.LOGINAME, sp.nt_username)   AS HostName    

      ,ec1.client_net_address                  AS ClientIpAddress

      ,db.name                                 AS DatabaseName        

      ,wt.wait_type                            AS WaitType                    

      ,ec1.connect_time                        AS BlockingStartTime

      ,wt.WAIT_DURATION_MS/1000                AS WaitDuration

      ,ec1.session_id                          AS BlockedSessionId

      ,h1.TEXT                                 AS BlockedSQLText

      ,h2.TEXT                                 AS BlockingSQLText

FROM sys.dm_tran_locks AS tl

INNER JOIN sys.databases db

  ON db.database_id = tl.resource_database_id

INNER JOIN sys.dm_os_waiting_tasks AS wt

  ON tl.lock_owner_address = wt.resource_address

INNER JOIN sys.dm_exec_connections ec1

  ON ec1.session_id = tl.request_session_id

INNER JOIN sys.dm_exec_connections ec2

  ON ec2.session_id = wt.blocking_session_id

LEFT OUTER JOIN master.dbo.sysprocesses sp

  ON SP.spid = wt.blocking_session_id

CROSS APPLY sys.dm_exec_sql_text(ec1.most_recent_sql_handle) AS h1

CROSS APPLY sys.dm_exec_sql_text(ec2.most_recent_sql_handle) AS h2

如下图所,我们可以看到阻塞其它会话以及被阻塞会话的信息,如下所示

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

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

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

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

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

正好这段时间,我在YourSQLDba上扩展一些功能,于是我将这个存储过程放置在YouSQLDba数据库中。

USE [YourSQLDba]

GO

 

IF NOT EXISTS(SELECT * FROM sys.objects WHERE object_id=OBJECT_ID(N'[Maint].[BlockingSQLHistory]') AND type='U')

BEGIN

CREATE TABLE Maint.BlockingSQLHistory

(               

                RecordTime                        DATETIME           ,

                DatabaseName                      SYSNAME            ,

                BlockingSessesionId               SMALLINT           ,

                ProgramName                       NCHAR(128)         ,

                UserName                          NCHAR(256)         ,

                ClientIpAddress                   VARCHAR(48)        ,

                WaitType                          NCHAR(60)          ,

                BlockingStartTime                 DATETIME           ,

                WaitDuration                      BIGINT             ,

                BlockedSessionId                  INT                ,        

                BlockedSQLText                    NVARCHAR(MAX)      ,

                BlockingSQLText                   NVARCHAR(MAX)      ,

                CONSTRAINT PK_BlockingSQLHistory  PRIMARY KEY(RecordTime)

)

 

END

GO

存储过程如下所示:

USE [YourSQLDba]

GO

 

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[Maint].[sp_who_blocking]') AND type in (N'P', N'PC'))

DROP PROCEDURE [Maint].[sp_who_blocking]

GO

 

 

 

 

 

SET ANSI_NULLS ON

GO

 

SET QUOTED_IDENTIFIER ON

GO

 

 

 

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

--        ProcedureName         :            [Maint].[sp_who_blocking]

--        Author                :            Kerry    http://www.cnblogs.com/kerrycode/

--        CreateDate            :            2014-04-23

--        Description           :            监控数据库阻塞情况,显示阻塞会话信息或收集阻塞会话信息或发送告警邮件

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

        Parameters                   :                                    参数说明

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

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

            @EmailSubject    :            邮件主题.默认为Sql Blocking Alert,一般指定,例如“ServerName Sql Blocking Alert"

            @ProfileName     :            @profile_name 默认值为YourSQLDba_EmailProfile

            @RecipientsLst   :            收件人列表

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

   Modified Date    Modified User     Version                 Modified Reason

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

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


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

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

CREATE PROCEDURE [Maint].[sp_who_blocking]

(

        @OutType    

            VARCHAR(8) ='Default'                  ,

        @EmailSubject         

            VARCHAR(120)='Sql Blocking Alert'      ,

        @ProfileName         

            sysname='YourSQLDba_EmailProfile'      ,

        @RecipientsLst 

             VARCHAR(MAX) = NULL

)

AS

BEGIN

 

SET NOCOUNT ON;

 

DECLARE @HtmlContent  NVARCHAR(MAX) ;

 

    IF @OutType NOT IN ('Default', 'Table','Email')

    BEGIN

        PRINT 'The parameter @OutType is not correct,please check it';

        

        return;

    END

 

    IF @OutType ='Default'

        BEGIN

        

              SELECT db.name                                 AS DatabaseName    

                    ,wt.blocking_session_id                  AS BlockingSessesionId

                    ,sp.program_name                         AS ProgramName

                    ,COALESCE(sp.LOGINAME, sp.nt_username)   AS UserName    

                    ,ec1.client_net_address                  AS ClientIpAddress    

                    ,wt.wait_type                            AS WaitType                    

                    ,ec1.connect_time                        AS BlockingStartTime

                    ,wt.WAIT_DURATION_MS/1000                AS WaitDuration

                    ,ec1.session_id                          AS BlockedSessionId

                    ,h1.TEXT                                 AS BlockedSQLText

                    ,h2.TEXT                                 AS BlockingSQLText

              FROM sys.dm_tran_locks AS tl

              INNER JOIN sys.databases db

                ON db.database_id = tl.resource_database_id

              INNER JOIN sys.dm_os_waiting_tasks AS wt

                ON tl.lock_owner_address = wt.resource_address

              INNER JOIN sys.dm_exec_connections ec1

                ON ec1.session_id = tl.request_session_id

              INNER JOIN sys.dm_exec_connections ec2

                ON ec2.session_id = wt.blocking_session_id

              LEFT OUTER JOIN master.dbo.sysprocesses sp

                ON SP.spid = wt.blocking_session_id

              CROSS APPLY sys.dm_exec_sql_text(ec1.most_recent_sql_handle) AS h1

              CROSS APPLY sys.dm_exec_sql_text(ec2.most_recent_sql_handle) AS h2;

         END

     ELSE IF @OutType='Table'

        BEGIN

     

              INSERT INTO [Maint].[BlockingSQLHistory]

              SELECT GETDATE()                               AS RecordTime

                    ,db.name                                 AS DatabaseName    

                    ,wt.blocking_session_id                  AS BlockingSessesionId

                    ,sp.program_name                         AS ProgramName

                    ,COALESCE(sp.LOGINAME, sp.nt_username)   AS UserName    

                    ,ec1.client_net_address                  AS ClientIpAddress

                    ,wt.wait_type                            AS WaitType                    

                    ,ec1.connect_time                        AS BlockingStartTime

                    ,wt.WAIT_DURATION_MS/1000                AS WaitDuration

                    ,ec1.session_id                          AS BlockedSessionId

                    ,h1.TEXT                                 AS BlockedSQLText

                    ,h2.TEXT                                 AS BlockingSQLText

              FROM sys.dm_tran_locks AS tl

              INNER JOIN sys.databases db

                ON db.database_id = tl.resource_database_id

              INNER JOIN sys.dm_os_waiting_tasks AS wt

                ON tl.lock_owner_address = wt.resource_address

              INNER JOIN sys.dm_exec_connections ec1

                ON ec1.session_id = tl.request_session_id

              INNER JOIN sys.dm_exec_connections ec2

                ON ec2.session_id = wt.blocking_session_id

              LEFT OUTER JOIN master.dbo.sysprocesses sp

                ON SP.spid = wt.blocking_session_id

              CROSS APPLY sys.dm_exec_sql_text(ec1.most_recent_sql_handle) AS h1

              CROSS APPLY sys.dm_exec_sql_text(ec2.most_recent_sql_handle) AS h2;

         END

      ELSE IF @OutType='Email'

         BEGIN

 

            SET @HtmlContent =

               N'<head>' 

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

             + N'<table border="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 ( 

                    (SELECT db.name                                  AS TD, ''

                           ,wt.blocking_session_id                   AS TD, ''

                           ,sp.program_name                          AS TD, ''

                           ,COALESCE(sp.LOGINAME, sp.nt_username)    AS TD, ''

                           ,ec1.client_net_address                   AS TD, ''

                           ,wt.wait_type                             AS TD, ''            

                           ,ec1.connect_time                         AS TD, ''

                           ,wt.WAIT_DURATION_MS/1000                 AS TD, ''

                           ,ec1.session_id                           AS TD, ''

                           ,h1.TEXT                                  AS TD, ''

                           ,h2.TEXT                                  AS TD, ''

 

                    FROM sys.dm_tran_locks AS tl

                    INNER JOIN sys.databases db 

                            ON db.database_id = tl.resource_database_id

                    INNER JOIN sys.dm_os_waiting_tasks AS wt  

                            ON tl.lock_owner_address = wt.resource_address

                    INNER JOIN sys.dm_exec_connections ec1 

                            ON ec1.session_id = tl.request_session_id

                    INNER JOIN sys.dm_exec_connections ec2 

                            ON ec2.session_id = wt.blocking_session_id

                    LEFT OUTER JOIN master.dbo.sysprocesses sp

                            ON SP.spid = wt.blocking_session_id

                    CROSS APPLY sys.dm_exec_sql_text(ec1.most_recent_sql_handle) AS h1

                    CROSS APPLY sys.dm_exec_sql_text(ec2.most_recent_sql_handle) AS h2

                

               FOR XML PATH('tr'), TYPE 

                ) AS NVARCHAR(MAX) ) +

                N'</table>'

 

 

                IF @HtmlContent  IS NOT NULL

                 

                BEGIN

 

                    EXEC msdb.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   

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

                                       ( CASE WHEN er.statement_end_offset = -1

                                              THEN LEN(CONVERT(NVARCHAR(MAX), qt.text))

                                                   * 2

                                              ELSE er.statement_end_offset

                                         END - er.statement_start_offset ) / 2) 

        ,[Parent Query] = qt.text 

        ,[PROGRAM_NAME] = program_name 

FROM    sys.dm_exec_requests er

        INNER JOIN sys.sysprocesses sp ON er.session_id = sp.spid

        CROSS APPLY sys.dm_exec_sql_text(er.sql_handle) AS qt

WHERE   session_Id = @sessionid;

SQL Server 监控统计阻塞脚本信息的更多相关文章

  1. SQL Server监控清单

    SQL Server监控清单 一. 服务器1. 状态监控(1) 服务器是否可访问?(2) 相应的数据库服务是否启用?(3) 操作系统事件日志中的错误或告警(4) 磁盘可用空间 服务器状态监控,不管使用 ...

  2. c#Winform程序调用app.config文件配置数据库连接字符串 SQL Server文章目录 浅谈SQL Server中统计对于查询的影响 有关索引的DMV SQL Server中的执行引擎入门 【译】表变量和临时表的比较 对于表列数据类型选择的一点思考 SQL Server复制入门(一)----复制简介 操作系统中的进程与线程

    c#Winform程序调用app.config文件配置数据库连接字符串 你新建winform项目的时候,会有一个app.config的配置文件,写在里面的<connectionStrings n ...

  3. SQL Server镜像自动生成脚本

    SQL Server镜像自动生成脚本 镜像的搭建非常繁琐,花了一点时间写了这个脚本,方便大家搭建镜像 执行完这个镜像脚本之后,最好在每台机器都绑定一下hosts文件,不然的话,镜像可能会不work 1 ...

  4. SQL Server 监控系列(文章索引)

    一.前言(Introduction) SQL Server监控在很多时候可以帮助我们了解数据库做了些什么,比如谁谁在什么时候修改了表结构,谁谁在删除了某个对象,当这些事情发生了,老板在后面追着说这是谁 ...

  5. SQL SERVER 2008R2 执行大脚本文件时,管理工具提示“内存不足”的解决方法

    项目需求:当我把服务器上的数据库导出为SQL脚本时,在本地新建数据库,再导入执行SQL文件时报错,因为SQL文件过大,导致出现如下报错 如下图所示: ========================= ...

  6. SQL SERVER 2008R2 执行大脚本文件时,提示“内存不足”的解决办法

    我把一个数据库的架构及数据都已脚本的方式拷贝下来,再去新建一个数据库想把脚本执行一下,但提示如下错误: 问题描述: 当客户服务器不允许直接备份时,往往通过导出数据库脚本的方式来部署-还原数据库, 但是 ...

  7. SQL Server 等待统计信息基线收集

    背景 我们随时监控每个服务器不同时间段的wait statistics ,可以根据监控信息大概判断什么时候开始出现异常,相当于一个wait statistics基线收集,还可以具体分析占比高的等待类型 ...

  8. SQL Server 中统计信息直方图中对于没有覆盖到谓词预估以及预估策略的变化(SQL2012-->SQL2014-->SQL2016)

    本位出处:http://www.cnblogs.com/wy123/p/6770258.html 统计信息写过几篇了相关的文章了,感觉还是不过瘾,关于统计信息的问题,最近又踩坑了,该问题虽然不算很常见 ...

  9. SQL Server 查找统计信息的采样时间与采样比例

    有时候我们会遇到,由于统计信息不准确导致优化器生成了一个错误的执行计划(或者这样表达:一个较差的执行计划),从而引起了系统性能问题.那么如果我们怀疑这个错误的执行计划是由于统计信息不准确引起的.那么我 ...

随机推荐

  1. SPRING多个占位符配置文件解析源码研究--转

    原文地址:http://www.cnphp6.com/archives/85639 Spring配置文件: <context:property-placeholder location=&quo ...

  2. ZOJ Problem Set - 1334 Basically Speaking ac代码及总结

    这道题目不难,是一道简单的进制转换问题,但是发现了自己两个遗漏的知识点: 1.关于scanf (1)scanf函数在输入时是以回车或者空格作为一次输入的结束 (2)scanf函数在输入字符串的过程中是 ...

  3. 1Z0-053 争议题目解析512

    1Z0-053 争议题目解析512 考试科目:1Z0-053 题库版本:V13.02 题库中原题为: 512.Which two statements correctly describe the r ...

  4. 十大经典排序算法总结(JavaScript描述)

    前言 读者自行尝试可以想看源码戳这,博主在github建了个库,读者可以Clone下来本地尝试.此博文配合源码体验更棒哦~~~ 个人博客:Damonare的个人博客 原文地址:十大经典算法总结 这世界 ...

  5. N皇后问题

    题目描述 在n×n格的棋盘上放置彼此不受攻击的n个皇后.按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子.n后问题等价于再n×n的棋盘上放置n个后,任何2个皇后不妨在同一行或同 ...

  6. Python爬取代理ip

    # -*- coding:utf-8 -*- #author : willowj import urllib import urllib2 from bs4 import BeautifulSoup ...

  7. 转载:《TypeScript 中文入门教程》 9、泛型

    版权 文章转载自:https://github.com/zhongsp 建议您直接跳转到上面的网址查看最新版本. 介绍 软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性. 组件不 ...

  8. Thinking in Java——笔记(19)

    Enumerated Types Basic enum features When you create an enum, an associated class is produced for yo ...

  9. 新建 ASP.NET Core MVC 项目 -- Hello World!

    一.创建一个空项目 请查看 新建 .NET Core 项目 -- Hello World! 一节,新建一个项目: 二.添加引用并修改配置为 MVC 修改 .vscode\launch.json 文件 ...

  10. 从“黑掉Github”学Web安全开发

    Egor Homakov(Twitter: @homakov 个人网站: EgorHomakov.com)是一个Web安全的布道士,他这两天把github给黑了,并给github报了5个安全方面的bu ...