SQL Server 扩展事件(Extented Events)从入门到进阶(4)——扩展事件引擎——基本概念

186 篇文章 6 订阅
130 篇文章 237 订阅

本文属于 SQL Server 扩展事件(Extented Events)从入门到进阶 系列


在第一二节中,我们创建了一些简单的、类似典型SQL Trace的扩展事件会话。在此过程中,介绍了很多扩展事件基础组件,包括事件、谓词、操作和目标。本节,将对扩展事件引擎、架构和基本组件做更加深入的了解。通过这些讲解,可以大概了解到为什么扩展事件相对于SQL Trace来说更加低开销。另外,还会延时如何设计事件会话从而最小化事件收集过程中的不必要开销,即使这些事件会话会很复杂。


事件数据收集生命周期:

扩展事件(Extended Events,XE)中的事件,可以发生在SQL Server进程中的很多地方,每个事件在遇到特定的事件代码时都会触发。比如有些事件是在存储过程开始执行或者编译时触发、遇到死锁时触发、统计信息自动更新时触发、锁申请或释放时触发等。
当正在执行的任务触发事件时,我们希望能够简单地,从任务执行线程直接传入XE引擎。它必须为每个活动事件做以下处理:
  1. 收集事件的基本负载数据。
  2. 评估谓词
  3. 如果谓词为True则收集对应操作。
  4. 直接分派事件数据到所有同步目标或者到中间内存缓冲区等待分派到异步目标。
一旦所有数据被收集完毕。控制流程返回执行线程。前面提到过,XE引擎是重新设计和重写现有高开销的Trace架构。其目标就是最小化事件采集过程中的性能压力,比如最小化在采集过程中,任务执行线程放弃控制以便事件采集数据可以顺利进行的时间。
在第一节中提到过,扩展事件主要使用下面方式来实现最小化影响:
  • 最小化在默认负载(相对于Trace这种收集事件所有列而言)中收集的事件列数。
  • 在事件数据采集之前使用谓词预先过滤掉不必要的数据。
  • 通过先进的预聚合(pre-aggregating)可用性目标有时候可以显著降低引擎可能需要收集的数据总量。
但是不管怎样,在创建事件会话是还是需要考虑很多东西以免像SQL Trace那样伤害SQL Server性能。下面将详细介绍各个相关组件。


扩展事件引擎基础架构:

XE引擎,属于SQLOS的一部分,是一个用于创建事件对象集合、事件会话创建、管理及处理事件数据捕获的服务。前面提到过的组件:特别是事件、操作、谓词和目标,都不属于核心引擎。这些对象,以及类型和映射,都存在于包(packet)里面,包驻留在SQL Server的各种模组(modules)中,如exe文件、DLLs等。这些模组(如sqlos.dll、sqldk.dll、sqlservr.exe)在实例启动时,把扩展事件引擎一并注册,然后引擎通过包进行交互,使得不同的事件、操作、谓词、目标、映射和类型可用。

模组和包(Modules and Packages):

包被加载到XE引擎从而为我们在定义事件会话时使用的不同的XE对象提供服务。下面第一个语句用于查找现有的包,并列出从加载的模组列表:


   
   
SELECT [ p ].[ name ] AS [ Module ] ,
[ p ].[ description ] AS [ Description ] ,
[ m ].[ name ] AS [ ModulePath ]
FROM [ sys ].[ dm_xe_packages ] [ p ]
JOIN [ sys ].[ dm_os_loaded_modules ] [ m ]
ON [ p ].[ module_address ] = [ m ].[ base_address ];
GO

下面是本机的结果:

如果是在SQL 2016上运行,会有13行。而SQL 2008只有4行。本机是SQL 2014。下面的语句用于显示每个包加载了哪些事件:
SELECT  [xo].[name] AS [EventName] ,
        [xo].[description] AS [EventDescription] ,
        [xp].[name] AS [Package]
FROM    [sys].[dm_xe_objects] [xo]
        JOIN [sys].[dm_xe_packages] [xp]
        ON [xo].[package_guid] = [xp].[guid]
WHERE   [xo].[object_type] = N'event'
ORDER BY [xo].[name];
GO

本机结果如下:



事件:

扩展事件,正如其名,其基础组件当然就是“事件(events)”,我们可以创建没有任何操作、谓词甚至目标的事件会话,但是必须有最少一个事件。
SQL 2016 CU1提供了1303种不同的事件,以“通道(channel)”形式组织。这四个channel为:Admin、Operational、AnalyticDebug。没有必要记住哪个事件属于哪个channel,但是注意默认情况下只有前面三个channel是GUI默认显示的。而Debug在GUI中除非手动显示否则默认不现实的,并且没有办法使其默认显示。sys.dm_xe_objects 视图可以显示所有channel的所有事件。
当你添加一个事件到事件会话时,每当事件会话开始运行,并且特定的事件被触发,控制器都会把事件数据传到扩展事件引擎中。
每当事件触发时,第一个操作就是收集每个事件会话中定义的默认元素集数据。也就是每个事件的默认负载。默认负载是事件的最小列集,并且这个默认负载通常随着事件的不同而不同。这种方式通过只收集必要的数据从而减少整体开销。在Trace中,事件的所有列都会被收集然后再根据筛选条件筛选。
下面脚本用于收集某个事件的默认数据元素,比如sql_statement_completed事件:

   
   
SELECT [ object_name ] AS [ EventName ] ,
[ name ] AS [ ElementName ] ,
[ column_id ] AS [ ColumnID ] ,
[ type_name ] AS [ ElementType ] ,
[ column_type ] AS [ ColumnType ] ,
[ capabilities_desc ] AS [ Capability ] ,
[ description ] AS [ ElementDescription ]
FROM [ sys ].[ dm_xe_object_columns ]
WHERE [ object_name ] = N 'sql_statement_completed'
AND [ column_type ] <> 'readonly' ;
GO
除了脚本形式,还可以用GUI方式查看,比如 第二篇中提到的【事件】页,默认负载是不可定制化的,除了下图的红框那些:




事件列(也叫数据列)包含了列类型。我们可以把它们作为默认负载的一部分来收集,当然也可以不收集。
虽然扩展事件通常来说开销都比Trace小,但是也还是有一些事件会明显影响性能。比如 showplan就是特别需要注意的,比如下图中红框部分明确提醒了:



即使加上了谓词筛选, showplan依旧具有很高开销,因为 showplan_xml字段是属于默认负载。因此,在谓词生效之前,必须收集执行计划的XML数据,这个操作是一个高开销操作。

操作:

如果我们需要收集事件数据中的其他不在默认负载中的列,或者希望事件触发时同时触发另外一个操作,那么就需要显式指定一个合适的操作到事件会话中。
一旦事件触发,XE引起会收集事件的默认负载(包括里面已经启用的可选列)。并对事件会话进行谓词评估。只有事件触发并符合谓词定义的数据才会被收集,并且只有此时被 执行的操作才会被收集,这种设计还是为了最小化负载。下面脚本用于返回可用的操作:


   
   
SELECT [ xp ].[ name ] AS [ Package ] ,
[ xo ].[ name ] AS [ Action ] ,
[ xo ].[ description ] AS [ Description ]
FROM [ sys ].[ dm_xe_packages ] AS [ xp ]
JOIN [ sys ].[ dm_xe_objects ] AS [ xo ]
ON [ xp ].[ guid ] = [ xo ]. package_guid
WHERE ( [ xp ].[ capabilities ] IS NULL
OR [ xp ].[ capabilities ] & 1 = 0
)
AND ( [ xo ].[ capabilities ] IS NULL
OR [ xo ].[ capabilities ] & 1 = 0
)
AND [ xo ].[ object_type ] = 'action' ;
GO

GUI中,这些操作显示在【全局字段(操作)】当中。可以把上面语句返回的列表中的操作加到你的事件会话定义中,但有些特定的操作可能在某些事件触发时不可用,比如“query_plan_hash”操作对“sp_statement_starting”事件不可用。【操作】用于收集对事件有用的额外信息,但是需要考虑引入的额外开销。默认负载属于事件的一部分,所以事件触发时数据已经可用。对于符合事件定义的谓词为True的操作,会“同步”执行。意味着XE引擎必须在任务执行线程中收集这些数据或者执行其他操作。根据操作的类型及数量,或多或少会影响性能。比如,添加database_id这个操作,比添加tsql_stack操作开销就低很多。同时,根据执行的频率,和查询本身(比如用了用户自定义函数),tsql_stack操作可能影响任务执行。另外,操作毕竟增加了事件所收集的数据量,并且一些操作需要更多的存储空间(如callstack 操作就需要比cpu_id更多的存储空间)。所以在配置事件会话时,记得指定事件的最大大小。如果这个值太小,可能收集不到事件数据。
有些操作(action)还会执行一些额外的操作(operation,比如收集事件的状态信息、收集内存转储(memory dump)。这些副作用操作,比如内存转储或调试断点,不应该用于常规事件会话。只有在极端情况下才应该在生产环境中使用。

谓词:

谓词在事件触发时起到筛选作用,相对于SQL Trace,扩展事件的谓词具有强大和细粒度的筛选能力。在Trace中,设置过滤条件,依旧会针对Trace中的所有事件生效。在XE中,可以对每个事件做独立的谓词定义。同时Trace中,无法实现谓词的AND/OR限定,而XE的其中一个关键特性就是能够尽快地执行“短路”事件,使事件尽快地返回控制任务执行的线程。更深层次而言,XE的谓词是在事件触发时马上生效,仅会收集满足谓词逻辑的操作,这也是XE优于Trace(收集后过滤)的另外一个地方。
下面语句演示了一个非常简单的谓词,确保事件会话可以收集sp_statement_completed事件,但是只有逻辑读大于10000的语句才收集。而对于小于这个值的语句,使用短路逻辑过滤掉,不执行任何操作(actions):


   
   
CREATE EVENT SESSION [ MyEventSession ] ON SERVER
ADD EVENT sqlserver . sp_statement_completed ( SET collect_object_name = ( 1 ) ,
collect_statement = ( 1 )
ACTION ( sqlserver . client_app_name , sqlserver . database_name )
WHERE ( [ logical_reads ] >= ( 10000 ) ) );
GO

我们可以使用布尔表达式来创建谓词的逻辑块(logical blocks),这些逻辑块非常关键的,因为一旦谓词块中的逻辑评估为false,那么评估会停止,同时事件不触发。下面语句对上面的事件会话定义添加了一个AND逻辑,为了触发事件,逻辑读必须大于等于10000并且持续事件必须小于1秒(1000000 微妙)。


   
   
CREATE EVENT SESSION [ MyEventSession ] ON SERVER
ADD EVENT sqlserver . sp_statement_completed ( SET collect_object_name = ( 1 ) ,
collect_statement = ( 1 )
ACTION ( sqlserver . client_app_name , sqlserver . database_name )
WHERE ( [ logical_reads ] >= ( 10000 )
AND [ duration ] >= ( 1000000 )
) );
GO

持续时间可以为微妙或毫秒,可以通过下面语句来确定:

SELECT
   [object_name] AS [Event],
   [name] AS [Column],
   [description] AS [Description]
FROM  [sys].[dm_xe_object_columns]
WHERE [name] = 'duration'
ORDER BY [object_name];

本机结果如下:


如果任何谓词检测为False,那么事件都不会触发,这里就要 注意谓词的顺序。比如前面的例子中,logical_reads在第一个,意味着如果语句的逻辑读小于10000,那么就会发生短路逻辑,直接跳过对持续事件的评估。如果条件反过来,那么会先检测在一个存储过程中任何语句是否小于1秒,如果都小于1秒,同样会短路,不评估逻辑读。通过这种方式可以降低收集的开销。
前面的例子中,我们只是对事件的默认负载中的事件字段定义谓词。但是我们同样可以对全局字段(操作)定义谓词。由于操作的发生在任务执行线程中是和事件触发同步的,所以XE引擎在这个过程中也必须评估操作的谓词。如果这些操作中的谓词评估也会false,那么事件的数据收集就会终止。如果为True,那么所有操作都会被执行,然后把数据分派到目标或者中间内存缓冲区中。下面这个代码就是一个例子:

CREATE EVENT SESSION [MyEventSession] ON SERVER
ADD EVENT sqlserver.sp_statement_completed (  SET collect_object_name = ( 1 ) ,
                                              collect_statement = ( 1 )
    ACTION ( sqlserver.client_app_name, sqlserver.database_name )
    WHERE ( [sqlserver].[database_id] = ( 7 )
            AND [logical_reads] >= ( 10000 )
            AND [duration] >= ( 1000 )
          ) );
GO

database_id首先触发,XE引擎收集database_id数据列,并只对值为7的触发。如果我们需要收集的大部分语句都不在这个数据库中,那么这个列作为谓词的第一列就不够高效了。

映射(Maps):

在T-SQL中编写谓词条件需要对映射(Maps)有一定的了解。映射表示在引擎中常用值的名称与另一种格式的关系。比如wait_info事件,用于获取SQL Server中关于等待信息的内容。如果你想获取关于“WRITELOG”等待的信息,并且持续时间大于1秒,你需要添加wait_info事件到事件会话中,然后在“WRITELOG”中wait_type配置谓词。在UI界面中这个操作倒不是什么问题,但是如果用T-SQL来编写,或者也担心谓词最大字符限制(3000个字符),那么就需要把WRITELOG映射成数值型格式。
可以使用下面语句查找相关信息:

SELECT  [xmv].[name] ,
        [xmv].[map_key] ,
        [xmv].[map_value]
FROM    sys.dm_xe_map_values [xmv]
        JOIN sys.dm_xe_packages [xp]
        ON [xmv].[object_package_guid] = [xp].[guid]
WHERE   [xmv].[name] = N'wait_types';
GO

本机结果如下:
语句返回了所有“wait_type”的“map_key”和他对应的“map_values”及对应的描述。然后我们就可以把上面的要求改写成:

CREATE EVENT SESSION [Capture WRITELOG Waits] ON SERVER
ADD EVENT sqlos.wait_info (
    WHERE ( ( [wait_type] = ( 181) )
            AND ( [duration] >= ( 1000 ) )
          ) )
ADD TARGET package0.event_file ( SET filename = N'C:\temp\WaitInfo' );
GO

如果使用复杂和很长的谓词,使用map_key会更加有效。但是由于这个映射关系可能在补丁升级时变更,所以使用时需要校验。

目标:

在配置了合适的事件、操作和谓词之后,我们通常需要选择目标来存储事件会话采集的数据。【目标】是一个事件的“消费者”,是事件数据的最终目的地。目标可以同步地消耗事件,如在相同线程中触发事件,或者异步地消耗。在扩展事件中有6类可用目标:
  • event_file:类似SQL Trace中的Trace文件的文件系统目标,在事件会话结束后固化到硬盘中。
  • ring_buffer:使用FIFO(First In First Out,先进先出)算法,把数据驻留在内存存储中。
  • event_counter:在内存中,每个事件会话中的事件出现频率数。
  • histogram:在内存中的目标,产生一个关于事件发生的柱状图,关于所有常用事件的每个数据列或者操作,或者一个特定事件或者操作的一列的信息。
  • pair_matching:在内存中,匹配目标的开始和结束事件,只保留没有配对的那些事件(比如有事务开始事件,没有事务结束事件)
除此之外还有一个“ Event Tracing For Windows(ETW)”目标,用于微软团队做深入研究,所以在这里不打算介绍。对于event_file和ring_buffer目标,都是异步的,并且以原始数据形式采集。而event_counter目标是同步的,仅收集一个事件符合谓词的触发次数。
histogram和pair-matching目标也是异步的,并且不存储原始数据格式,但是以某些方式存储聚合数据,这个方式基于配置。

event_file:

event_file目标存在硬盘,并且事件在会话启用时开始写入硬盘。在事件会话停止后固化到硬盘中,可用于后续分析。这个数据以二进制格式存储数据,并且在GUI显示时以XML链接显示。
在SQL 2008/2008R2中,event_file数据存储在两个文件中,一个保存数据,一个保存元数据,在SQL 2012及以上版本,只有一个.xel文件,当事件引擎创建文件目标时,会在文件名后面追加一个数值型,标识从事件启动到1700-1-1开始的描述。确保文件名创建时是唯一的。
在创建event_file目标时,应该设置最大文件大小,也应该设置【文件滚动更新】中的【最大文件数】。

ring_buffer:

ring_buffer目标存在内存中,基于FIFO算法存储数据,ring_buffer的最大缓冲区内存大小按MB计算,同时也可以指定要保留的事件数。需要知道,由于默认负载和某些配置操作,有些事件可能不能适应内存分配的大小。当ring_buffer满了,旧的事件数据会被移除,新的事件数据会被载入。
因为数据以二进制存储,所以必须使用XQuery分析ring_buffer中的数据。根据事件发生频率和内存分配的数量,可以通过不停的查询可以显示不同的数据。同时,所有存在于ring_buffer的数据不能被显示,因为sys.dm_xe_session_targets DMV中的一个问题,不能超过4MB。基于这个原因。ring_buffer数据一旦事件会话结束即被清出内存。所以ring_buffer不适合用于综合分析事件数据。





Event_counter:


event_counter是异步的,仅收集事件触发的次数,并且前提是满足谓词的事件。这个目标是内存驻留的,同样事件停止后,数据也会清空。这个目标通常用于验证事件会话是否使用了合适的事件(简单来说就是用于Troubleshooting事件配置)。也可以在你对服务器的负载不确定的情况下用于协助检测。
使用这个目标可以看出事件基于谓词的触发频率。

Histogram:




这个目标在SQL 2008/2008R2中称为“bucketizer”,因为它基于选择的元素进行分组,比如你想知道为什么语句重编译,可以使用下面语句捕获:

CREATE EVENT SESSION [Capture WRITELOG Waits] ON SERVER
ADD EVENT sqlos.wait_info (
    WHERE ( ( [wait_type] = ( 181 ) )
            AND ( [duration] >= ( 1000 ) )
          ) )
ADD TARGET package0.event_file ( SET filename = N'C:\temp\WaitInfo' );
GO

CREATE EVENT SESSION [TrackRecompiles] ON SERVER
ADD EVENT sqlserver.sql_statement_recompile (  SET collect_object_name = ( 1 ) ,
                                               collect_statement = ( 1 )
    WHERE ( [sqlserver].[is_system] = ( 0 ) ) )
ADD TARGET package0.histogram (  SET filtering_event_name = N'sqlserver.sql_statement_recompile' ,
                                 slots = ( 11 ) ,
                                 source = N'recompile_cause' ,
                                 source_type = ( 0 ) )
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 = OFF );
GO

输出到GUI的结果讲列出每个非系统重编译事件的recompile_cause(数值)和一个触发的时间值。一旦会话停止,数据就被移除。其中一个限制就是你只能在一个元素上进行分组。在前面提到过,对那些查找哪些object被重编译时,对object_id分组很有用。如果希望对多个元素分组,只能使用event_file或ring_buffer,然后手动计算。同时,在histogram目标中,只能有256个唯一buckets(由于中文版SSMS也没有对这个词进行翻译,所以这里也保留原词)。如上面语句中的slots=(11),显示了11个不同的重编译原因。

Pair_matching:

pair_matching目标需要两个事件,开始事件和结束事件。当开始事件没有匹配一个结束事件,就会被记录在目标中。事件中的一个或多个元素可以被匹配,这些元素非常重要,因为可以用于确保事件是否丢失或者捕获异常。需要注意的是,这个“开始”和“结束”事件,不总是一一对应的关系。比如lock acquired和lock released事件,在锁升级的时候,关系就不是一一对应了。



使用pair_matching的常见用处是侦测超时和一些attention问题,可以使用sql_statement_starting和sql_statement_completed事件。注意匹配不总是在session_id上,也可以在tsql_stack上,需要两个一起使用:

CREATE EVENT SESSION [Find_Unmatched_Statements] ON SERVER
ADD EVENT sqlserver.sql_statement_starting (
    ACTION ( sqlserver.session_id, sqlserver.tsql_stack ) ),
ADD EVENT sqlserver.sql_statement_completed (
    ACTION ( sqlserver.session_id, sqlserver.tsql_stack ) )
ADD TARGET package0.pair_matching  (  SET begin_event = N'sqlserver.sql_statement_starting' ,
        begin_matching_actions = N'sqlserver.session_id, sqlserver.tsql_stack' ,
        end_event = N'sqlserver.sql_statement_completed' ,
        end_matching_actions = N'sqlserver.session_id, sqlserver.tsql_stack' ,
        respond_to_memory_pressure = ( 0 )
 )
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 = OFF );
GO

不匹配的事件和操作信息会记录在内存目标中,可以通过GUI或TSQL查询目标,但是当事件会话结束后,数据就被清空。

总结:

本节是目前系列的最后一节,若后续作者有更新将继续翻译。本节介绍了关于事件、操作、谓词和目标及扩展事件的基本使用情况。使用不同的目标,搭配不同的谓词、事件,可以对SQL Server进行深入的、轻量级的监控分析,也可以理解SQL Server的工作机制。
微软通过对扩展事件进行重写,使其更加低开销、高度可扩展。
在配置会话事件是,注意下面几点:
  • 如非必要不要添加事件。
  • 不要添加不必要的操作。
  • 在谓词中使用短路逻辑最小化数据收集的开销,特别在收集大量事件,并且事件被频繁触发,或采集的事件本身就是高开销的。
  • 根据目的选择必要的目标,并且考虑存储时效。
最后,记住扩展事件并不是“仅仅”为了替代Trace。它既覆盖了绝大部分Trace功能,也提供了侦测问题的新方向。









  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值