如何有效抓取SQL Server的BLOCKING信息

原文:如何有效抓取SQL Server的BLOCKING信息

转自:微软亚太区数据库技术支持组 官方博客

http://blogs.msdn.com/b/apgcdsd/archive/2011/12/12/sql-server-blocking.aspx

SQL Server允许并发操作,BLOCKING是指在某一操作没有完成之前,其他操作必须等待,以便于保证数据的完整性。BLOCKING的解决方法要查看BLOCKING的头是什么,为什么BLOCKING头上的语句执行的很慢。通常来讲只要我们能找到BLOCKING头上的语句,我们总能够想出各种各种的办法,来提升性能,缓解或解决BLOCKING的问题。

但是问题的关键是,我们不知道BLOCKING什么时候会发生。用户跟我们抱怨数据库性能很差,等我们连上数据库去查看的时候,那时候有可能BLOCKING可能就已经过去了。性能又变好了。或者由于问题的紧急性,我们直接重新启动服务器以恢复运营。但是问题并没有最终解决,我们不知道下次问题会在什么时候发生。

BLOCKING问题的后果比较严重。因为终端用户能直接体验到。他们提交一个订单的时候,无论如何提交不上去,通常几秒之内能完成的一个订单提交,甚至要等待十几分钟,才能提交完成。更有甚者,极严重的BLOCKING能导致SQL Server停止工作。如下面的SQL ERRORLOG所表示, 在短短的几分钟之内,SPID数据从158增长到694, 并马上导致SQL Server打了一个dump, 停止工作。我们很容易推断出问题的原因是由于BLOCKING导致的,但是我们无法得知BLOCKING HEADER是什么,我们必须要等下次问题重现时,辅之以工具,才能得知BLOCKING
HEADER在做什么事情。如果信息抓取时机不对,我们可能要等问题发生好几次,才能抓到。这时候,客户和经理就会有抱怨了。因为我们的系统是生产系统,问题每发生一次,都会对客户带来损失。

2011-06-01 16:22:30.98 spid1931    Alert There are 158 Active database sessions which is too high.

2011-06-01 16:23:31.16 spid3248    Alert There are 342 Active database sessions which is too high.

2011-06-01 16:24:31.06 spid3884    Alert There are 517 Active database sessions which is too high.

2011-06-01 16:25:31.08 spid3688    Alert There are 694 Active database sessions which is too high.

2011-06-01 16:26:50.93 Server      Using ‘dbghelp.dll‘ version ‘4.0.5‘

2011-06-01 16:26:50.97 Server      **Dump thread - spid = 0, EC = 0x0000000000000000

2011-06-01 16:26:50.97 Server      ***Stack Dump being sent to D:\MSSQL10.INSTANCE\MSSQL\LOG\SQLDump0004.txt

2011-06-01 16:26:50.97 Server      * *******************************************************************************

2011-06-01 16:26:50.97 Server      *

2011-06-01 16:26:50.97 Server      * BEGIN STACK DUMP:

2011-06-01 16:26:50.97 Server      *   06/01/11 16:26:50 spid 4124

2011-06-01 16:26:50.97 Server      *

2011-06-01 16:26:50.97 Server      * Deadlocked Schedulers

2011-06-01 16:26:50.97 Server      *

2011-06-01 16:26:50.97 Server      * *******************************************************************************

2011-06-01 16:26:50.97 Server      * -------------------------------------------------------------------------------

2011-06-01 16:26:50.97 Server      * Short Stack Dump

2011-06-01 16:26:51.01 Server      Stack Signature for the dump is 0x0000000000000258

BLOCKING的信息抓取有很多种方法。这里罗列了几种。并且对每种分析它的优缺点。以便我们选择。在枚举方法之前,我们先简单演示一下BLOCKING.

我们首先创建一个测试表:

DROP TABLE [TESTTABLE]

GO

CREATE TABLE [dbo].[TESTTABLE](

[ID] [int] NULL,

[NAME] [nvarchar](50) NULL

)

GO

INSERT INTO TESTTABLE VALUES (1, ‘aaaa‘)

GO

然后打开一个查询窗口,执行下面的语句, 该语句修改一行数据,并等待3分钟,然后在结束transaction

BEGIN TRANSACTION

UPDATE TESTTABLE SET [NAME] = ‘bbbb‘ WHERE [ID] = 1

WAITFOR  DELAY ‘00:03:00‘

COMMIT TRANSACTION

这时候,如果打开另外一个查询窗口,执行下面的语句,下面的语句就会被BLOCK住。

UPDATE TESTTABLE SET [NAME] = ‘cccc‘ WHERE [ID] = 1

方法一, 抓取SQL Profiler

======================

SQL Profiler里面包含大量的信息。其中有一个事件在Errors and Warnings->Blocked Process Report专门用来获得blocking的情况。但是因为信息量比较大,而且我们并不能很好的估算在什么时候会产生blocking,另外在生产环境使用Profiler, 对性能可能会有影响,所以SQL Profiler并不是最合适的工具。我们在这里并不对它赘述。

方法二, 执行查询

================

如果我们检查问题的时候,blocking还存在,那么,我们可以直接可以运行几个查询,得知BLOCKING HEADER的信息

SELECT * FROM sys.sysprocesses where spid>50

上述查询只是告诉我们,BLOCKING HEADER的头是SPID=53, 但是并没有告诉我们SPID=53在做什么事情。我们可以用下面的查询,得到SPID=53的信息

DBCC INPUTBUFFER(53)

我们可以把上述的两个查询合并起来,用下面的查询:

SELECT SPID=p.spid,

DBName = convert(CHAR(20),d.name),

ProgramName = program_name,

LoginName = convert(CHAR(20),l.name),

HostName = convert(CHAR(20),hostname),

Status = p.status,

BlockedBy = p.blocked,

LoginTime = login_time,

QUERY = CAST(TEXT AS VARCHAR(MAX))

FROM   MASTER.dbo.sysprocesses p

INNER JOIN MASTER.dbo.sysdatabases d

ON p.dbid = d.dbid

INNER JOIN MASTER.dbo.syslogins l

ON p.sid = l.sid

CROSS APPLY sys.dm_exec_sql_text(sql_handle)

WHERE  p.blocked = 0

AND EXISTS (SELECT 1

FROM   MASTER..sysprocesses p1

WHERE  p1.blocked = p.spid)

这样,一次执行,就能告诉我们BLOCKING header的SPID信息,以及该SPID在做的语句。我们可以进一步研究该语句,以理解为什么该语句执行很慢。

用这个方法有一个缺点,就是使用的时候,要求BLOCKING是存在的。如果BLOCKING已经消失了,那么我们的方法就不管用了。

方法三,长期执行一个BLOCKING SCRIPT

==================================

因为我们通常无法知道BLOCKING什么时候会产生,所以通常的办法是我们长期运行一个BLOCKING SCRIPT, 这样,等下次发生的时候,我们就会有足够的信息。长期运行BLOCKING SCRIPT对性能基本上是没有影响的。因为我们每隔10秒钟抓取一次信息。缺点是,如果问题一个月才发生一次,那么,我们的BLOCKING日志信息会很大。所以这种方法适用于几天之内能重现问题。

运行方法如下:

如果要停止运行,我们按ctrl+c就可以了。

BLOCKING的信息存在log.out这个文件中

我们可以打开log.out这个文件, 会发现SPID 54被 SPID 53给Block住了。

而随后,我们可以看到SPID=53在做什么事情:

下面是BLOCKING SCRIPT的脚本, 我们可以把它存为blocking.sql

use master

go

while 1 =1

begin

print ‘Start time: ‘ + convert(varchar(26), getdate(), 121)

Print ‘Running processes‘

select spid, blocked, waittype, waittime, lastwaittype, waitresource, dbid,uid, cpu, physical_io, memusage, login_time, last_batch,

open_tran, status, hostname, program_name, cmd, net_library, loginame

from sysprocesses

--where (kpid <> 0 ) or (spid < 51)

-- Change it if you only want to see the working processes

print ‘*********lockinfor***********‘

select convert (smallint, req_spid) As spid,

rsc_dbid As dbid,

rsc_objid As ObjId,

rsc_indid As IndId,

substring (v.name, 1, 4) As Type,

substring (rsc_text, 1, 16) as Resource,

substring (u.name, 1, 8) As Mode,

substring (x.name, 1, 5) As Status

from master.dbo.syslockinfo,

master.dbo.spt_values v,

master.dbo.spt_values x,

master.dbo.spt_values u

where master.dbo.syslockinfo.rsc_type = v.number

and v.type = ‘LR‘

and master.dbo.syslockinfo.req_status = x.number

and x.type = ‘LS‘

and master.dbo.syslockinfo.req_mode + 1 = u.number

and u.type = ‘L‘

order by spid

print ‘inputbuffer for running processes‘

declare @spid varchar(6)

declare ibuffer cursor fast_forward for

select cast (spid as varchar(6)) as spid from sysprocesses where spid >50

open ibuffer

fetch next from ibuffer into @spid

while (@@fetch_status != -1)

begin

print ‘‘

print ‘DBCC INPUTBUFFER FOR SPID ‘ + @spid

exec (‘dbcc inputbuffer (‘ + @spid + ‘)‘)

fetch next from ibuffer into @spid

end

deallocate ibuffer

waitfor delay ‘0:0:10‘

End

这种方法的缺陷就是,log.out会比较巨大,会占用很大的空间,如果blocking一个月甚至更长时间才发生一次,那我们的这个方法就不太适宜。

方法四,我们用Agent Job来检查BLOCKING

=====================================

长期运行一个BLOCKING SCRIPT的缺点是我们每隔一段时间,去查询信息,但是大多数收集的信息是无用的。所以会导致日志文件巨大,对于一个生产系统来讲,磁盘空间满可不是个好事情,另外,有一些客户对于用命令行来长期运行TSQL脚本有所顾忌,所以我们做了一个改进。这次,我们只收集有用的信息。对于无用的信息我们不关注。这样能极大减少日志大小。

我们首先创建一个观察数据库,然后建立两张表格 Blocking_sysprocesses和Blocking_SQLText, 建立一个存储过程和一个Job, 该Job每隔一段时间去调用存储过程。只有发现有blocking的,我们才记录到表格Blocking_sysprocesses和Blocking_SQLText这两个表格中。如果跟blocking无关,我们就不对它进行记录。下面是TSQL语句:

CREATE DATABASE [MonitorBlocking]

GO

USE [MonitorBlocking]

GO

CREATE TABLE Blocking_sysprocesses(

[spid] smallint,

[kpid] smallint,

[blocked] smallint,

[waitType] binary(2),

[waitTime] bigInt,

[lastWaitType] nchar(32),

[waitResource] nchar(256),

[dbID] smallint,

[uid] smallint,

[cpu] int,

[physical_IO] int,

[memusage] int,

[login_Time] datetime,

[last_Batch] datetime,

[open_Tran] smallint,

[status] nchar(30),

[sid] binary(86),

[hostName] nchar(128),

[program_Name] nchar(128),

[hostProcess] nchar(10),

[cmd] nchar(16),

[nt_Domain] nchar(128),

[nt_UserName] nchar(128),

[net_Library] nchar(12),

[loginName] nchar(128),

[context_Info] binary(128),

[sqlHandle] binary(20),

[CapturedTimeStamp] datetime

)

GO

CREATE TABLE [dbo].[Blocking_SqlText](

[spid] [smallint],

[sql_text] [nvarchar](2000),

[Capture_Timestamp] [datetime]

)

GO

CREATE PROCEDURE [dbo].[checkBlocking]

AS

BEGIN

SET NOCOUNT ON;

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

declare @Duration   int -- in milliseconds, 1000 = 1 sec

declare @now            datetime

declare @Processes  int

select  @Duration = 100  -- in milliseconds, 1000 = 1 sec

select  @Processes = 0

select @now = getdate()

CREATE TABLE #Blocks_rg(

[spid] smallint,

[kpid] smallint,

[blocked] smallint,

[waitType] binary(2),

[waitTime] bigInt,

[lastWaitType] nchar(32),

[waitResource] nchar(256),

[dbID] smallint,

[uid] smallint,

[cpu] int,

[physical_IO] int,

[memusage] int,

[login_Time] datetime,

[last_Batch] datetime,

[open_Tran] smallint,

[status] nchar(30),

[sid] binary(86),

[hostName] nchar(128),

[program_Name] nchar(128),

[hostProcess] nchar(10),

[cmd] nchar(16),

[nt_Domain] nchar(128),

[nt_UserName] nchar(128),

[net_Library] nchar(12),

[loginName] nchar(128),

[context_Info] binary(128),

[sqlHandle] binary(20),

[CapturedTimeStamp] datetime

)

INSERT INTO #Blocks_rg

SELECT

[spid],

[kpid],

[blocked],

[waitType],

[waitTime],

[lastWaitType],

[waitResource],

[dbID],

[uid],

[cpu],

[physical_IO],

[memusage],

[login_Time],

[last_Batch],

[open_Tran],

[status],

[sid],

[hostName],

[program_name],

[hostProcess],

[cmd],

[nt_Domain],

[nt_UserName],

[net_Library],

[loginame],

[context_Info],

[sql_Handle],

@now as [Capture_Timestamp]

FROM master..sysprocesses where blocked <> 0

AND waitTime > @Duration

SET @Processes = @@rowcount

INSERT into #Blocks_rg

SELECT

src.[spid],

src.[kpid],

src.[blocked],

src.[waitType],

src.[waitTime],

src.[lastWaitType],

src.[waitResource],

src.[dbID],

src.[uid],

src.[cpu],

src.[physical_IO],

src.[memusage],

src.[login_Time],

src.[last_Batch],

src.[open_Tran],

src.[status],

src.[sid],

src.[hostName],

src.[program_name],

src.[hostProcess],

src.[cmd],

src.[nt_Domain],

src.[nt_UserName],

src.[net_Library],

src.[loginame],

src.[context_Info],

src.[sql_Handle]

,@now as [Capture_Timestamp]

FROM  master..sysprocesses src inner join #Blocks_rg
trgt

on trgt.blocked = src.[spid]

if @Processes > 0

BEGIN

INSERT [dbo].[Blocking_sysprocesses]

SELECT * from #Blocks_rg

DECLARE @SQL_Handle binary(20), @SPID smallInt;

DECLARE cur_handle CURSOR FOR SELECT sqlHandle, spid FROM #Blocks_rg;

OPEN cur_Handle

FETCH NEXT FROM cur_handle INTO @SQL_Handle, @SPID

WHILE (@@FETCH_STATUS = 0)

BEGIN

INSERT [dbo].[Blocking_SqlText]

SELECT      @SPID, CONVERT(nvarchar(4000), [text]) ,@now as [Capture_Timestamp]from ::fn_get_sql(@SQL_Handle)

FETCH NEXT FROM cur_handle INTO @SQL_Handle, @SPID

END

CLOSE cur_Handle

DEALLOCATE cur_Handle

END

DROP table #Blocks_rg

END

GO

USE msdb;

GO

EXEC dbo.sp_add_job

@job_name = N‘MonitorBlocking‘;

GO

EXEC sp_add_jobstep

@job_name = N‘MonitorBlocking‘,

@step_name = N‘execute blocking script‘,

@subsystem = N‘TSQL‘,

@command = N‘exec checkBlocking‘,

@database_name=N‘MonitorBlocking‘;

GO

EXEC sp_add_jobSchedule

@name = N‘ScheduleBlockingCheck‘,

@job_name = N‘MonitorBlocking‘,

@freq_type = 4, -- daily

@freq_interval = 1,

@freq_subday_type = 4,

@freq_subday_interval = 1

EXEC sp_add_jobserver @job_name = N‘MonitorBlocking‘, @server_name = N‘(local)‘

当Blocking发生一段时间后,我们可以查询下面的两个表格,以得知当时问题发生时的blocking信息:

use MonitorBlocking

GO

SELECT * from Blocking_sqlText

SELECT * FROM Blocking_sysprocesses

时间: 2024-10-25 21:48:55

如何有效抓取SQL Server的BLOCKING信息的相关文章

java抓取12306火车余票信息

最近在弄一个微信的公众帐号,涉及到火车票查询,之前用的网上找到的一个接口,但只能查到火车时刻表,12306又没有提供专门的查票的接口.今天突然想起自己直接去12306上查询,抓取查询返回的数据包,这样就可以得到火车票的信息.这里就随笔记一下获取12306余票的过程. 首先,我用firefox浏览器上12306查询余票.打开firefox的Web控制台,选上网络中的"记录请求和响应主体" 然后输入地址日期信息之后点击网页上的查询按钮,就能在Web控制台下看到网页请求的地址了: 就是图片中

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

原文:SQL Server 中统计信息直方图中对于没有覆盖到谓词预估以及预估策略的变化(SQL2012-->SQL2014-->SQL2016) 本文出处:http://www.cnblogs.com/wy123/p/6770258.html 统计信息写过几篇了相关的文章了,感觉还是不过瘾,关于统计信息的问题,最近又踩坑了,该问题虽然不算很常见,但也比较有意思.相对SQL Server 2012,发现在新的SQL Server版本(2014,2016)中都有一些明显的变化,下文将对此进行粗浅的

SQL Server删除表信息的三种方法

1.使用DELETE实现SQL Server删除表信息 (1)删除表中的全部信息 USE student GO DELETE student      --不加where条件,删除表中的所有记录 go (2)删除表中符合条件的记录 USE student GO DELETE student where Id='001'    --删除表中符合条件的记录 GO 2.使用TRUNCATE删除表中的信息 USE student GO TRUNCATE TABLE    student   --删除表中

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

原文:SQL Server 查找统计信息的采样时间与采样比例 有时候我们会遇到,由于统计信息不准确导致优化器生成了一个错误的执行计划(或者这样表达:一个较差的执行计划),从而引起了系统性能问题.那么如果我们怀疑这个错误的执行计划是由于统计信息不准确引起的.那么我们如何判断统计信息不准确呢?当然首先得去查看实际执行计划中,统计信息的相关数据是否与实际情况有较大的出入,下面我们抛开这个大命题,仅仅从统计信息层面去查看统计信息的更新时间,统计信息的采样行数.采样比例等情况. 1:首先,我们要查查统计信

Python 抓取微信公众号账号信息

搜狗微信搜索提供两种类型的关键词搜索,一种是搜索公众号文章内容,另一种是直接搜索微信公众号.通过微信公众号搜索可以获取公众号的基本信息及最近发布的10条文章,今天来抓取一下微信公众号的账号信息( 爬虫 首先通过首页进入,可以按照类别抓取,通过"查看更多"可以找出页面链接规则: import requests as req import re reTypes = r'id="pc_\d*" uigs="(pc_\d*)">([\s\S]*?)

scrapy自动抓取蛋壳公寓最新房源信息并存入sql数据库

利用scrapy抓取蛋壳公寓上的房源信息,以北京市为例,目标url:https://www.dankegongyu.com/room/bj 思路分析 每次更新最新消息,都是在第一页上显示,因此考虑隔一段时间自动抓取第一页上的房源信息,实现抓取最新消息. 利用redis的set数据结构的特征,将每次抓取后的url存到redis中: 每次请求,将请求url与redis中的url对比,若redis中已存在该url,代表没有更新,忽略该次请求:若redis中不存在该url,代表该信息是新信息,抓取并将u

SQL Server 服务器器信息备份(一)--login新建脚本备份

前言 若你的企业使用SQL Server数据库镜像为容灾技术. 那你一定做过在镜像切换之前要新建Login,而且若Login密码不同,要修改链接数据库的字符串,在切换完之后则仍需要给数据库重新赋予权限. 若真的是这样做,不仅麻烦而且业务故障时间将会拉长,我们需要做到新建的Login与之前的密码完全一样,而且可自动根据原有数据库用户链接到Login,数据库级别权限不用从新处理. 思路 有了这样的思路则Login的信息备份需要考虑两点: 1.一定要获取用户的SID,以保证镜像切换后能自动连接到log

SQL SERVER的统计信息

可以看到,统计信息分为三部分内容,头信息,数据字段选择性及直方图. 2.1 头信息 列名 说明 Name 统计信息的名称. Updated 上次更新统计信息的日期何时间 Rows 预估表中的行数,不一定是精确的 Rows Sampled 统计信息的抽样行数,如果小于Rows,则说明直方图和密度结果是更加抽样行估计的 Steps 直方图中的梯级数.Number of steps in the histogram.每个梯级都跨越一个列值范围,后跟上限列值. 直方图梯级是根据统计信息中的第一个键列定义

利用 pyspider 框架抓取猫途鹰酒店信息

利用框架 pyspider 能实现快速抓取网页信息,而且代码简洁,抓取速度也不错. 环境:macOS:Python 版本:Python3. 1.首先,安装 pyspider 框架,使用pip3一键安装: pip3 pyspider 2.终端输入 pyspider all 启动 pyspider: 打开 Chrome,地址栏输入 localhost:5000 进入 pyspider 框架的webui界面. 点击 create ,创建 一个新的project. 3.创建完 project 后,我们便