本文出处:http://www.cnblogs.com/wy123/p/6835939.html

什么是等待

简单说明一下什么是等待:
当应用程序对SQL Server发起一个Session请求的时候,这个Session请求在数据库中执行的过程中会申请其所需要的资源,
比如可能会申请内存资源,表上的锁资源,物理IO资源,网络资源等等,
如果当前Session运行过程中需要申请的某些资源无法立即得到满足,就会产生等待。
SQL Server会以不用的方式来展现这个等待信息,比活动Session的等待信息,实例级的等待信息等等。
SQL Server中,等待事件是作为DBA进行TroubleShooting的重要参考信息之一,SQL Server中可以通过多中方式来获取等待信息。

但是对于SQL Server 2016之前的版本来说,不管是活动Session级别的等待和实例级的等待,参考意义都有限,
更多的时候是想要更加详细的且可以事后分析的等待,这就需要收集那些曾经已执行过的Session产生的等待信息,也就是特定Session等待信息的历史记录
本文重点介绍使用扩展事件来捕获等待信息,但不介绍扩展事件本身的使用,重点放在如何使用扩展事件来获取想要的等待事件信息。
需要对扩展事件有一定的了解。

等待信息的获取途径

在SQL Server中有一个系统视图sys.dm_os_wait_stats记录了自数据库服务启动以来累积产生的等待信息,
如下图,这个结果是实例级的,也就是记录的整个数据库服务器所有的等待事件的累积。
多数时候参考意义不是很大,比如某一天的网络延时很高,sys.dm_os_wait_stats中累计记录了大量的ASYNC_NETWORK_IO等待信息。
但是到了第二天或者什么时候,网络变好了,但是sys.dm_os_wait_stats中记录的ASYNC_NETWORK_IO等待信息是不变的
也就是说sys.dm_os_wait_stats无法反映实时等待情况。

当然要获取实时的等待信息也简单,记录两个时间点之间sys.dm_os_wait_stats中等待时间的差值,可以间接地反映出来某一段时间的数据的等待信息。
但是这个信息仍然比较粗略,依旧是实例级的,某些时候依旧是不足够作为参考的。

另外一个是通过sys.dm_exec_requests这个系统视图的wait_type,wait_time等获取活动Session的等待信息
如截图,但是这个是活动Session的信息,当Session完成之后,它的等待信息就看不到曾经都产生了那些等待,分别是多久。
也就说,你无法追溯历史上某一个Session或者某一个SQL(存储过程)执行过程中的都产生了什么类型的等待,等待了多长时间。

实话说,不管是sys.dm_os_wait_stats还是sys.dm_exec_requests,在正常情况下,获取到的等待信息实用价值都是不高或者是适用场景有限。

更多的时候我们是想要更细一级的等待,比如某一个Login、某一部分Session、甚至某些特定的SQL(存储过程)的执行过程中产生的等待信息。
举个实际例子,数据库又10个Login给10个不同的应用程序访问,其中只有1个应用程序端反馈说访问数据库慢,或者有性能问题,其他Login都反馈正常
那么很有可能是这个Login请求自己的问题,此时就需要针对这一个Login的情况进行针对性分析,而不是在实例级分析诊断。
如果能够拿到这个Login执行的Session的等待情况,或者这个Login某些特定的数据库对象的执行过程中的等待信息,对定位问题的针对性的就比较强了。

本文就以此为切入点,针对如何获取Session级别的等待信息展开说明和演示。

SQL Server 2016中获取Session级别的等待信息

在SQL Server 2016中,获取Session级别的等待信息是比较方便的,有直接的系统视图sys.dm_exec_session_wait_stats可以使用
当前情况下,想要知道某一个Session的等待信息就很简单了,
在sql语句开始的时候把当前Session的等待信息记录下来
在sql语句结束的时候把当前Session的等待信息再次记录出来
计算两次等待信息的差值,就可以知道当前Session运行的过程中有哪些等待,分别是多少。

  sqlserver开发团队可能也意识到了对于等待信息,更多的时候,需要的是较为具体的等待,而不是一个笼统的实例级的等待
  因此在SQL Server 2016中增加了sys.dm_exec_session_wait_stats这个支持统计Session级别的等待的视图
  很不幸的SQL Server2016之前的版本中是没有这个系统视图可以很方便地记录Session级别的等待。
  但是可以借助扩展事件来实现类似的功能。

使用扩展事件来捕获Session级别的等待信息

因为这里是是用扩展事件来实现的,这里要求读者要对扩展事件有一个基本的认识,扩展事件本身就不多说了。
上代码,启动一个扩展事件,来记录执行时间超过三秒的SQL语句,其执行过程中等待时间大于0的等待事件信息。
当然这个捕获的信息可以加上各种过滤条件。具体参考代码备注。

IF EXISTS(SELECT * FROM sys.server_event_sessions WHERE name='SessionWaitStats')
DROP EVENT session SessionWaitStats ON SERVER;
GO -- 创建事件会话
CREATE EVENT SESSION SessionWaitStats ON SERVER
ADD EVENT sqlserver.rpc_completed
(
ACTION
(
sqlos.task_time,
sqlserver.database_name,
sqlserver.nt_username,
sqlserver.username,
sqlserver.client_hostname,
sqlserver.client_app_name,
sqlserver.sql_text,
sqlserver.session_id,
sqlserver.transaction_id
) WHERE session_id>50
and [duration]>=3000000
),
--ADD EVENT sqlserver.sql_statement_completed
--(
-- ACTION
-- (
-- sqlos.task_time,
-- sqlserver.database_name,
-- sqlserver.nt_username,
-- sqlserver.client_hostname,
-- sqlserver.client_app_name,
-- sqlserver.username,
-- sqlserver.sql_text,
-- sqlserver.session_id,
-- sqlserver.transaction_id
-- ) WHERE session_id>50
-- and [duration]>=3000000
--),
ADD EVENT sqlserver.sql_batch_completed
(
ACTION
(
sqlos.task_time,
sqlserver.database_name,
sqlserver.nt_username,
sqlserver.client_hostname,
sqlserver.client_app_name,
sqlserver.username,
sqlserver.sql_text,
sqlserver.session_id,
sqlserver.transaction_id
) WHERE session_id>50
and [duration]>=3000000
),
ADD EVENT sqlos.wait_info
(
ACTION
(
sqlos.task_time,
sqlserver.database_name,
sqlserver.nt_username,
sqlserver.client_hostname,
sqlserver.client_app_name,
sqlserver.sql_text,
sqlserver.username,
sqlserver.session_id,
sqlserver.transaction_id
) WHERE session_id>50
and opcode=1
and duration>1
and sql_text not like '%sp_MScdc_capture_job%'
--and username = ''
),
ADD EVENT sqlos.wait_info_external
(
ACTION
(
sqlos.task_time,
sqlserver.database_name,
sqlserver.nt_username,
sqlserver.username,
sqlserver.client_hostname,
sqlserver.client_app_name,
sqlserver.sql_text,
sqlserver.session_id,
sqlserver.transaction_id
) WHERE session_id>50
and opcode=1
and duration>1
and sql_text not like '%sp_MScdc_capture_job%'
--and username = ''
)
ADD TARGET package0.event_file
(
SET filename=N'F:\XEvent Files\CollectionSessionWaitStats',
max_file_size=(1024),
max_rollover_files=(10)
)
WITH (
MAX_MEMORY=4096 KB,
EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,
MAX_DISPATCH_LATENCY=30 SECONDS,
MAX_EVENT_SIZE=0 KB,
MEMORY_PARTITION_MODE=NONE,
TRACK_CAUSALITY=OFF,
STARTUP_STATE=ON
)
GO -- 启用(停止)事件会话(START / STOP)
ALTER EVENT SESSION CollectionSessionWaitStats ON SERVER STATE=START
GO

是用扩展事件收集的Session级别的等待信息有以下几个特点
1,SessionId是可以重复的
  举例说明就是:比如一个SessionId = 80的Session,可以第一次执行一个SQLA,第二次执行一个SQLB.
  同时这两个SQL的执行与其执行过程中产生的等待信息都可以被捕获出来
2,同一个时间段内,一个同一个SessionId不可能同时执行,
  比如16:46:36秒到16:46:46秒到这个时间内,SessionId = 80的Session正在执行,扩展事件捕获到了其产生的等待信息
  但是在16:46:36秒到16:46:46这个时间段内,不可能有另外一个SessionId = 80也在执行,这个逻辑不难理解
为什么要特意说明这个问题?
因为扩展事件收集到的事件信息中SQL语句完成事件(rpc_completed或者sql_batch_completed)与产生的等待之间,没有一个直接的对应关系。

怎么理解?动起手来才能发现问题,我是这个纠结了大半天,截图示例。

如下截图,扩展事件捕获到的两个目标事件的SessionId都是58,但是执行的SQL是不一样的,

如下截图是不区分事件类型,捕获到的所有的事件信息。

也就是上面说的,对于58号Session:
第一次运行了SELECT COUNT(1) FROM TestCollectionSessionWaitStats
第二次运行了SELECT COUNT(1) FROM TestCollectionSessionWaitStats WHERE Id>10
两次运行的SQL一致或者不一致问题不大,关键是捕获到的等待事件信心的SessionId也是58,怎么区分产生的事件是归属于哪一次的运行?
比如58号Session运行两次sql,产生了10条等待信息,怎么区分这10条等待信息哪些归属于第一次运行生成的,哪些归属于第二次运行生成的?
这个就依靠上面说的第二点“同一个时间段内,一个同一个SessionId不可能同时执行”
对于同一个SessionId,在sql_batch_completed事件中,从时间的维度来看
小于第一次sql_batch_completed事件完成时间的必然是58号Session第一次执行生成的
大于第一次sql_batch_completed事件完成事件的且小于第二次sql_batch_completed的必然是58号Session第二次执行生成的
有了这个理论基础,我们统计Session级别的等待就比较容易了,相信这个逻辑的实现并不难。

如下图是58号Session执行某SQL语句产生的等待详细信息,可以看到多次产生了CXPACKET和 PAGEIOLATCH_SH等待
鉴于截图问题,下面还有另外一个同样是58号Session的执行另外一个SQL捕获到的等待信息
这个统计办法就是上面提到的,在两次sql_batch_completed事件中,虽然等待事件的SessionId一样,
但是其发生的时候是处于当前事件的sql_batch_completed之前,上一次sql_batch_completed之后,
这样就可以完美地匹配到sql_batch_completed事件与其对应的wait_info事件。
在这种情况下,统计得到类似于SQL Server 2016中的sys.dm_os_wait_stats的结果也就不难了。

  

与sys.dm_os_wait_stats 等待信息的结果相比,上述通过扩展事件获取的等待信息,是不是更加详细和具体?
比如对于CXPACKET等待时间,
不难发现,如果计算计算其产生的次数(count),就类似于sys.dm_os_wait_stats 中的waiting_tasks_count,计算其产生的总时间(sum),就类似于wait_time_ms
但是上述时间的信息已经细化到Session级别了,比sys.dm_os_wait_stats 中的等待信息更有参考价值。
对于问题的诊断和分析,也会是更加有效。

  补一张实际测试出来的信息,分别是Session的执行信息(SessionId+登录名+SQL语句+消耗时间+完成时间)对应的等待信息(等待类型+等待次数+等待时间),
  是不是几乎完全类似于sys.dm_exec_session_wait_stats中统计出来的结果。

上述统计结果的SQL语句

/*

SELECT event_table.xml_event_data
FROM (
SELECT CAST(event_data AS XML) xml_event_data
FROM sys.fn_xe_file_target_read_file(N'F:\XEvent Files\CollectionSessionWaitStats*', NULL, NULL, NULL)
) AS event_table
CROSS APPLY xml_event_data.nodes('//event') n (event_xml)
WHERE event_xml.value('(./@name)', 'varchar(1000)') IN ('rpc_completed','sql_batch_completed','sql_statement_completed') */ -- Parse the XML to show rpc_completed,sql_batch_completed details
if object_id('tempdb..#t1') is not null
drop table #t1
SELECT
event_xml.value('(./action[@name="session_id"]/value)[1]', 'INT') as session_id,
event_xml.value('(./@timestamp)', 'varchar(1000)') as timestamp,
event_xml.value('(./action[@name="sql_text"]/value)[1]', 'varchar(max)') as sql_text,
event_xml.value('(./action[@name="client_hostname"]/value)[1]', 'varchar(max)') as client_hostname,
event_xml.value('(./action[@name="client_app_name"]/value)[1]', 'varchar(max)') as client_app_name,
event_xml.value('(./@name)', 'varchar(1000)') as Event_Name,
event_xml.value('(./data[@name="duration"]/value)[1]', 'bigint') as Duration,
event_xml.value('(./data[@name="cpu_time"]/value)[1]', 'bigint') as cpu_time,
event_xml.value('(./data[@name="physical_reads"]/value)[1]', 'bigint') as physical_reads,
event_xml.value('(./data[@name="logical_reads"]/value)[1]', 'bigint') as logical_reads,
event_xml.value('(./action[@name="username"]/value)[1]', 'varchar(max)') as username
INTO #t1
FROM (
SELECT CAST(event_data AS XML) xml_event_data
FROM sys.fn_xe_file_target_read_file(N'F:\XEvent Files\SessionWaitStats*', NULL, NULL, NULL)
) AS event_table
CROSS APPLY xml_event_data.nodes('//event') n (event_xml)
WHERE event_xml.value('(./@name)', 'varchar(1000)') in ('rpc_completed','sql_batch_completed','sql_statement_completed')
order by Event_Name -- Parse the XML to show wait_info,wait_info_external details
if object_id('tempdb..#t2') is not null
drop table #t2
SELECT
cast(event_xml.value('(./@timestamp)', 'varchar(1000)') as DATETIME2) as timestamp,
event_xml.value('(./data[@name="duration"]/value)[1]', 'bigint') as duration,
event_xml.value('(./action[@name="session_id"]/value)[1]', 'INT') as session_id,
event_xml.value('(./data[@name="wait_type"]/text)[1]', 'VARCHAR(200)') as wait_type
INTO #t2
FROM (
SELECT CAST(event_data AS XML) xml_event_data
FROM sys.fn_xe_file_target_read_file(N'F:\XEvent Files\SessionWaitStats*', NULL, NULL, NULL)
) AS event_table
CROSS APPLY xml_event_data.nodes('//event') n (event_xml)
WHERE event_xml.value('(./@name)', 'varchar(1000)') in ('wait_info','wait_info_external') if object_id('tempdb..#t3') is not null
drop table #t3 SELECT
a.session_id AS SessionId,
a.sql_text AS SqlText,
a.username AS UserName,
a.client_hostname AS ClientHostname,
a.client_app_name as ClientAppName,
a.Duration AS TotalExecuteTime,
CAST(a.timestamp AS DATETIME2) AS CompletedTime,
CAST(b.timestamp AS DATETIME2) AS WaitTypeStartTime,
b.wait_type AS WaitType,
b.duration AS WaitDuration
INTO #t3
FROM #t1 a INNER JOIN #t2 b on a.session_id = b.session_id
and b.timestamp < a.timestamp
and b.timestamp > (
select top 1 timestamp from #t1 c
where a.session_id = a.session_id and a.timestamp > b.timestamp
order by a.timestamp
) if object_id('tempdb..#t4') is not null
drop table #t4 SELECT DISTINCT
SessionId ,
UserName ,
SqlText ,
TotalExecuteTime ,
CompletedTime ,
WaitType ,
COUNT(WaitType) over(partition by SessionId,SqlText,TotalExecuteTime,CompletedTime) WaitTypeCOUNT,
SUM(WaitDuration) over(partition by SessionId,SqlText,TotalExecuteTime,CompletedTime) WaitTypeTimes
INTO #t4
FROM #t3 SELECT CASE WHEN t.Rn = 1 THEN CAST(SessionId AS VARCHAR(20)) ELSE '' END AS SessionId,
CASE WHEN t.Rn = 1 THEN UserName ELSE '' END AS UserName,
CASE WHEN t.Rn = 1 THEN SqlText ELSE '' END AS SqlText,
CASE WHEN t.Rn = 1 THEN TotalExecuteTime ELSE '' END AS TotalExecuteTime,
CASE WHEN t.Rn = 1 THEN CAST(CompletedTime AS VARCHAR(20)) ELSE '' END AS CompletedTime,
WaitType ,
WaitTypeCOUNT ,
WaitTypeTimes
FROM
(
SELECT
ROW_NUMBER()OVER(PARTITION BY SessionId,CompletedTime ORDER BY CompletedTime) AS Rn,*
FROM #t4
)t

当然该语句仅供参考,目的是为了收集Session级的统计信息,当收集到Session级别的统计信息之后,具体的统计方式也不难。

总结
   等待事件可以帮助我们诊断SQL Server上的一些资源瓶颈,对于问题的处理和解决有着比较重要的参考意义,如果能够细化地收集等待事件,对于解决问题的参考意义会更大。
   本文通过一个简单的示例,使用扩展事件来收集SQL Server中一些特定场景下的等待信息,来更加有针对性地进行问题的诊断和识别,使得问题的分析更加高效和具有针对性。

  

SQLServer中使用扩展事件获取Session级别的等待信息以及SQLServer 2016中Session级别等待信息的增强的更多相关文章

  1. 如果正确读取SQL Server中的扩展事件?

        SQL Server中使用扩展事件捕捉所需的信息后,可以选择存放的位置.比如说内存或文件中,但无论存在哪里,其本质都是一个大XML.因此在SQL Server中读取该XML就是解析扩展事件结果 ...

  2. SQL Server中的扩展事件学习系列

    SQL Server 扩展事件(Extented Events)从入门到进阶(1)——从SQL Trace到Extented Events SQL Server 扩展事件(Extented Event ...

  3. 百度地图中使用mouseover事件获取经纬度时无法拿到鼠标所在位置的经纬度。

    用百度2.0的话使用mousemove 鼠标在地图区域移动过程中触发此事件.mouseover参数e中没有point参数

  4. SQL Server 扩展事件(Extented Events)从入门到进阶(2)——在GUI中创建基础扩展事件

    本文属于 SQL Server 扩展事件(Extented Events)从入门到进阶 系列 第一篇文章中提到了如何在Profiler中创建跟踪(trace),并以服务器端(server-side)跟 ...

  5. SQL 扩展事件

    在本篇,我通过使用新建“Session ”对话框来创建新的扩展事件会话.定义一个自己的扩展事件,动作和谓词,并且发布一个以收集事件数据为目的的会话. 首先从UI开始 在SQLServer2008R2以 ...

  6. SQLSERVER2012里的扩展事件初尝试(下)

    SQLSERVER2012里的扩展事件初尝试(下) SQLSERVER2012里的扩展事件初尝试(上) 我们继续文章扩展事件在Denali CTP3里的新UI(二)里的这个实验 脚本文件下载:http ...

  7. SQL Server 扩展事件

    SQL Server 扩展事件(Extended Event)是用于服务器的常规事件处理系统,是追踪SQL Server系统运行状态的神器,同时也是一个日志记录工具,扩展事件完全可以取代SQL追踪(S ...

  8. SQL Server扩展事件-- 使用system_health默认跟踪会话监控死锁

    SQL Server扩展事件(Extended Events)-- 使用system_health默认跟踪会话监控死锁 转自:http://blog.51cto.com/ultrasql/160037 ...

  9. 实战:sqlserver 2008 扩展事件-XML转换为标准的table格式

    --假设已经存在Event Session删除 IF EXISTS (SELECT * FROM sys.server_event_sessions WHERE name='MonitorLongQu ...

随机推荐

  1. 关于window service2008系统iis部署访问证书,内部错误

    近期因为在做微信支付系列,做到退款的时候需要通过把数据流通过证书post过去的时候,win7.win8.xp部署在iis都没问题.但是部署到服务器 2008的时候就出现了内部错误. 折腾许久,总算找到 ...

  2. 用SSE指令计算点乘和累加

    void sse_mul_float:两段内存float数据点乘,结果覆盖第一组内存. float sse_acc_float:一组内存float值累加. 注: 1. 没有考虑中间的精确问题,结果会有 ...

  3. pyqt4实现tab界面切换

    (转:https://segmentfault.com/q/1010000005143428)

  4. 【VB超简单入门】六、基本数据类型

    接下来要介绍VB的基本数据类型,为接下来学习变量和常量准备. 计算机只能处理二进制的数据,所以无论什么数据,在CPU里面处理都是一样的,类似101010这样的机器代码,但是让我们直接去写机器代码程序, ...

  5. 整合初步______SH

    什么是框架 在的J2EE开发中,经常会提到"框架"这个词汇,例如Spring,Struts,Webx等等都称之为J2EE开发框架.那么,什么是框架呢? 框架的英文为Framewor ...

  6. as3中去掉字符串两边的空格,换行符

     as3 去掉字符串两边的空格,换行符,方法一  ActionScript Code  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20   pub ...

  7. js遍历(获取)ul中的li

    <!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8&qu ...

  8. WPF中的RichTextBox

    原文链接:http://blog.csdn.net/wuzhengqing1/article/details/7010902 取出richTextBox里面的内容 第一种方法:将richTextBox ...

  9. TextView的几个属性

    1. android:autoLink 自动识别文本中包含的链接,如网址.邮箱.电话.地图等:属性值有如下几种: web------------ ------只识别网址 email---------- ...

  10. donet体系结构

    一.C#与.NET的关系 1.粗略地説,.net是一种在Windows平台上的编程架构----一种API.2.C#编译器专门用于.net,这表示用C#编写的所有代码总是使用.NET Framework ...