SQL Server 默认跟踪 -- 捕获事件详解
哪些具体事件默认跟踪文件能够捕获到?
--returns full list of events
SELECT * FROM sys.trace_events
--returns a full list of categories
SELECT * FROM sys.trace_categories
--returns a full list of subclass values
SELECT * FROM sys.trace_subclass_values
Database 事件
让我们先从第一个事件开始:Database事件。它的子类事件包括数据文件和日志文件的自动增长和收缩,以及数据库镜像状态的改变。监控文件的增长和收缩是非常重要的;它可能隐射出性能问题。每次文件的增长和收缩,SQL Server将会挂起等待磁盘系统去让文件再次可用。在这里,挂起halt,是指直到进程完成前是没有事务处理的。
有如下数据库事件被监控:
? Data file auto grow
? Data file auto shrink
? Database mirroring status change
? Log file auto grow
? Log file auto shrink
如下脚本可以列出数据文件的增长和收缩:
SELECT TE.name AS [EventName] , T.DatabaseName , t.DatabaseID , t.NTDomainName , t.ApplicationName , t.LoginName , t.SPID , t.Duration , t.StartTime , t.EndTime FROM sys.fn_trace_gettable(CONVERT(VARCHAR(150), ( SELECT TOP 1 f.[value] FROM sys.fn_trace_getinfo(NULL) f WHERE f.property = 2 )), DEFAULT) T JOIN sys.trace_events TE ON T.EventClass = TE.trace_event_id WHERE te.name = 'Data File Auto Grow' OR te.name = 'Data File Auto Shrink' ORDER BY t.StartTime ;
该脚本的输出不会告诉你为什么增长,但是会告诉你增长花了多长事件。(关于Duration列的单位,会根据SQL Server的版本,可能是milliseconds或者microseconds)
而且,我推荐扩展这个查询语句,搜索增长时间大于1秒的数据库。这只是一个引导,可以根据自己的需求去扩展。
这里是另一个返回日志增长和收缩的查询语句:
SELECT TE.name AS [EventName] , T.DatabaseName , t.DatabaseID , t.NTDomainName , t.ApplicationName , t.LoginName , t.SPID , t.Duration , t.StartTime , t.EndTime FROM sys.fn_trace_gettable(CONVERT(VARCHAR(150), ( SELECT TOP 1 f.[value] FROM sys.fn_trace_getinfo(NULL) f WHERE f.property = 2 )), DEFAULT) T JOIN sys.trace_events TE ON T.EventClass = TE.trace_event_id WHERE te.name = 'Log File Auto Grow' OR te.name = 'Log File Auto Shrink' ORDER BY t.StartTime ;
而且要记住,这个查询不会告诉你,是否你的初级DBA在收缩数据和日志文件。在默认跟踪,我们只能找到自动增长和自动收缩的事件,并且不是通过ALTER DATABASE语句触发的。
Errors and Warnings事件
现在,让我们来看下一个事件:Errors and Warnings事件。可以看到,也有丰富的子事件。
当写事件到SQL Server事件日志中时,Errorlog子事件触发。当哈希匹配操作或排序操作溢出到磁盘(因为磁盘子系统是最慢的,我们的查询将变得更慢),Hash warning和Sort warnings事件会发生。只有当“Auto create statistics”选项被设置为off时,Missing column statistics事件才会发生。在这里,SQL Server说明了它可能已经关闭了一个不好的执行计划。当两个表没有连接谓词,且当所有表不止一行时,Missing join predicate事件发生。可以得到慢查询或者不可预期的结果。
Errors and Warnings的子类包括:
? Errorlog
? Hash warning
? Missing Column Statistics
? Missing Join Predicate
? Sort Warning
输出错误的脚本:
SELECT TE.name AS [EventName] , T.DatabaseName , t.DatabaseID , t.NTDomainName , t.ApplicationName , t.LoginName , t.SPID , t.StartTime , t.TextData , t.Severity , t.Error FROM sys.fn_trace_gettable(CONVERT(VARCHAR(150), ( SELECT TOP 1 f.[value] FROM sys.fn_trace_getinfo(NULL) f WHERE f.property = 2 )), DEFAULT) T JOIN sys.trace_events TE ON T.EventClass = TE.trace_event_id WHERE te.name = 'ErrorLog'
注意,这个脚本没有EndTime或者Duration列。
另一个脚本输出排序和哈希警告:
SELECT TE.name AS [EventName] , v.subclass_name , T.DatabaseName , t.DatabaseID , t.NTDomainName , t.ApplicationName , t.LoginName , t.SPID , t.StartTime FROM sys.fn_trace_gettable(CONVERT(VARCHAR(150), ( SELECT TOP 1 f.[value] FROM sys.fn_trace_getinfo(NULL) f WHERE f.property = 2 )), DEFAULT) T JOIN sys.trace_events TE ON T.EventClass = TE.trace_event_id JOIN sys.trace_subclass_values v ON v.trace_event_id = TE.trace_event_id AND v.subclass_value = t.EventSubClass WHERE te.name = 'Hash Warning' OR te.name = 'Sort Warnings'
最后,脚本会输出失效的统计信息和谓词连接:
SELECT TE.name AS [EventName] , T.DatabaseName , t.DatabaseID , t.NTDomainName , t.ApplicationName , t.LoginName , t.SPID , t.StartTime FROM sys.fn_trace_gettable(CONVERT(VARCHAR(150), ( SELECT TOP 1 f.[value] FROM sys.fn_trace_getinfo(NULL) f WHERE f.property = 2 )), DEFAULT) T JOIN sys.trace_events TE ON T.EventClass = TE.trace_event_id WHERE te.name = 'Missing Column Statistics' OR te.name = 'Missing Join Predicate'
Full text 事件
Full text事件类显示的主要事件包括:如果Full-Text终止,你可以在事件日志中找到具体的消息;FT Crawl Started子事件表明请求已经被进程获得。FT Crawl Stopped表明要么成功完成要么被错误停止。
Full-Text事件包括:
? FT Crawl Aborted
? FT Crawl Started
? FT Crawl Stopped
这个脚本返回全文事件:
SELECT TE.name AS [EventName] , DB_NAME(t.DatabaseID) AS DatabaseName , t.DatabaseID , t.NTDomainName , t.ApplicationName , t.LoginName , t.SPID , t.StartTime , t.IsSystem FROM sys.fn_trace_gettable(CONVERT(VARCHAR(150), ( SELECT TOP 1 f.[value] FROM sys.fn_trace_getinfo(NULL) f WHERE f.property = 2 )), DEFAULT) T JOIN sys.trace_events TE ON T.EventClass = TE.trace_event_id WHERE te.name = 'FT:Crawl Started' OR te.name = 'FT:Crawl Aborted' OR te.name = 'FT:Crawl Stopped'
注意,DatabaseName列是null,所以我们得用DB_NAME()函数获得数据库名。
Objects 事件
现在真正的探测工作开始:Objects改变。在子类中,有Altered、Created和Deleted对象。这里也包含了索引重建、统计信息更新,到数据库删除。
Objects事件包括:
? Object Altered
? Object Created
? Object Deleted
下面的脚本显示了数据库中最近的操作对象:
SELECT TE.name , v.subclass_name , DB_NAME(t.DatabaseId) AS DBName , T.NTDomainName , t.NTUserName , t.HostName , t.ApplicationName , t.LoginName , t.Duration , t.StartTime , t.ObjectName , CASE t.ObjectType WHEN 8259 THEN 'Check Constraint' WHEN 8260 THEN 'Default (constraint or standalone)' WHEN 8262 THEN 'Foreign-key Constraint' WHEN 8272 THEN 'Stored Procedure' WHEN 8274 THEN 'Rule' WHEN 8275 THEN 'System Table' WHEN 8276 THEN 'Trigger on Server' WHEN 8277 THEN '(User-defined) Table' WHEN 8278 THEN 'View' WHEN 8280 THEN 'Extended Stored Procedure' WHEN 16724 THEN 'CLR Trigger' WHEN 16964 THEN 'Database' WHEN 16975 THEN 'Object' WHEN 17222 THEN 'FullText Catalog' WHEN 17232 THEN 'CLR Stored Procedure' WHEN 17235 THEN 'Schema' WHEN 17475 THEN 'Credential' WHEN 17491 THEN 'DDL Event' WHEN 17741 THEN 'Management Event' WHEN 17747 THEN 'Security Event' WHEN 17749 THEN 'User Event' WHEN 17985 THEN 'CLR Aggregate Function' WHEN 17993 THEN 'Inline Table-valued SQL Function' WHEN 18000 THEN 'Partition Function' WHEN 18002 THEN 'Replication Filter Procedure' WHEN 18004 THEN 'Table-valued SQL Function' WHEN 18259 THEN 'Server Role' WHEN 18263 THEN 'Microsoft Windows Group' WHEN 19265 THEN 'Asymmetric Key' WHEN 19277 THEN 'Master Key' WHEN 19280 THEN 'Primary Key' WHEN 19283 THEN 'ObfusKey' WHEN 19521 THEN 'Asymmetric Key Login' WHEN 19523 THEN 'Certificate Login' WHEN 19538 THEN 'Role' WHEN 19539 THEN 'SQL Login' WHEN 19543 THEN 'Windows Login' WHEN 20034 THEN 'Remote Service Binding' WHEN 20036 THEN 'Event Notification on Database' WHEN 20037 THEN 'Event Notification' WHEN 20038 THEN 'Scalar SQL Function' WHEN 20047 THEN 'Event Notification on Object' WHEN 20051 THEN 'Synonym' WHEN 20549 THEN 'End Point' WHEN 20801 THEN 'Adhoc Queries which may be cached' WHEN 20816 THEN 'Prepared Queries which may be cached' WHEN 20819 THEN 'Service Broker Service Queue' WHEN 20821 THEN 'Unique Constraint' WHEN 21057 THEN 'Application Role' WHEN 21059 THEN 'Certificate' WHEN 21075 THEN 'Server' WHEN 21076 THEN 'Transact-SQL Trigger' WHEN 21313 THEN 'Assembly' WHEN 21318 THEN 'CLR Scalar Function' WHEN 21321 THEN 'Inline scalar SQL Function' WHEN 21328 THEN 'Partition Scheme' WHEN 21333 THEN 'User' WHEN 21571 THEN 'Service Broker Service Contract' WHEN 21572 THEN 'Trigger on Database' WHEN 21574 THEN 'CLR Table-valued Function' WHEN 21577 THEN 'Internal Table (For example, XML Node Table, Queue Table.)' WHEN 21581 THEN 'Service Broker Message Type' WHEN 21586 THEN 'Service Broker Route' WHEN 21587 THEN 'Statistics' WHEN 21825 THEN 'User' WHEN 21827 THEN 'User' WHEN 21831 THEN 'User' WHEN 21843 THEN 'User' WHEN 21847 THEN 'User' WHEN 22099 THEN 'Service Broker Service' WHEN 22601 THEN 'Index' WHEN 22604 THEN 'Certificate Login' WHEN 22611 THEN 'XMLSchema' WHEN 22868 THEN 'Type' ELSE 'Hmmm???' END AS ObjectType FROM [fn_trace_gettable](CONVERT(VARCHAR(150), ( SELECT TOP 1 value FROM [fn_trace_getinfo](NULL) WHERE [property] = 2 )), DEFAULT) T JOIN sys.trace_events TE ON T.EventClass = TE.trace_event_id JOIN sys.trace_subclass_values v ON v.trace_event_id = TE.trace_event_id AND v.subclass_value = t.EventSubClass WHERE TE.name IN ( 'Object:Created', 'Object:Deleted', 'Object:Altered' ) -- filter statistics created by SQL server AND t.ObjectType NOT IN ( 21587 ) -- filter tempdb objects AND DatabaseID <> 2 -- get only events in the past 24 hours AND StartTime > DATEADD(HH, -24, GETDATE()) ORDERBY t.StartTime DESC ;
记住,SQL Server默认有5个跟踪文件,每个20MB,没有知道的方法修改。如果你的系统很繁忙,跟踪文件会快速循环(甚至几小时内),以至你无法捕获到改变。
Security Audit事件
另一部分默认跟踪是Security Audit。你可以下列事件列表可以看到,它是一个丰富的默认跟踪。通常,这些时间组告诉我们的是在我们系统中发生的重要安全事件。
Security Audit事件包括:
? Audit Add DB user event
? Audit Add login to server role event
? Audit Add Member to DB role event
? Audit Add Role event
? Audit Add login event
? Audit Backup/Restore event
? Audit Change Database owner
? Audit DBCC event
? Audit Database Scope GDR event (Grant, Deny, Revoke)
? Audit Login Change Property event
? Audit Login Failed
? Audit Login GDR event
? Audit Schema Object GDR event
? Audit Schema Object Take Ownership
? Audit Server Starts and Stops
让我们一步步操作:
? 创建一个SQL Server login
? 分配读权限到数据库的用户
通过运行下面的脚本,我们能跟踪用户在实例上创建的对象。
SELECT TE.name AS [EventName] , v.subclass_name , T.DatabaseName , t.DatabaseID , t.NTDomainName , t.ApplicationName , t.LoginName , t.SPID , t.StartTime , t.RoleName , t.TargetUserName , t.TargetLoginName , t.SessionLoginName FROM sys.fn_trace_gettable(CONVERT(VARCHAR(150), ( SELECT TOP 1 f.[value] FROM sys.fn_trace_getinfo(NULL) f WHERE f.property = 2 )), DEFAULT) T JOIN sys.trace_events TE ON T.EventClass = TE.trace_event_id JOIN sys.trace_subclass_values v ON v.trace_event_id = TE.trace_event_id AND v.subclass_value = t.EventSubClass WHERE te.name IN ( 'Audit Addlogin Event', 'Audit Add DB User Event', 'Audit Add Member to DB Role Event' ) AND v.subclass_name IN ( 'add', 'Grant database access' )
下面是我们创建登录,并给用户赋予读取权限后的结果:
可以看到,第一行显示在master数据库中登录的创建,以及创建者(SessionLoginName列)和创建用户(TargetLoginName列)。
下两行:创建数据库用户并授予它访问权限,并最后添加用户到DB角色。
记住,如果你添加用户到多个角色,且如果你赋予登录访问给多个数据库,然而你会看到对每一个事件会记录很多行在你的默认跟踪。
现在,让我们审核被删除的用户和登录,运行如下查询语句:
SELECT TE.name AS [EventName] , v.subclass_name , T.DatabaseName , t.DatabaseID , t.NTDomainName , t.ApplicationName , t.LoginName , t.SPID , t.StartTime , t.RoleName , t.TargetUserName , t.TargetLoginName , t.SessionLoginName FROM sys.fn_trace_gettable(CONVERT(VARCHAR(150), ( SELECT TOP 1 f.[value] FROM sys.fn_trace_getinfo(NULL) f WHERE f.property = 2 )), DEFAULT) T JOIN sys.trace_events TE ON T.EventClass = TE.trace_event_id JOIN sys.trace_subclass_values v ON v.trace_event_id = TE.trace_event_id AND v.subclass_value = t.EventSubClass WHERE te.name IN ( 'Audit Addlogin Event', 'Audit Add DB User Event', 'Audit Add Member to DB Role Event' ) AND v.subclass_name IN ( 'Drop', 'Revoke database access' )
可以看到,对于创建和删除登录,事件名是相同的:Audit Addlogin Event;然而子类列值定义的不同:在创建登录子类是“Add”,而在删除子类是“Drop”。
事实上,如果我们删除之前创建的数据库用户和登录,这个查询会返回两行—对于每一个事件返回一行,包含被删除的用户和登录的名字,以及删除者的登录名。
下面的查询显示在默认跟踪文件中所有的失败的登录:
SELECT TE.name AS [EventName] , v.subclass_name , T.DatabaseName , t.DatabaseID , t.NTDomainName , t.ApplicationName , t.LoginName , t.SPID , t.StartTime , t.SessionLoginName FROM sys.fn_trace_gettable(CONVERT(VARCHAR(150), ( SELECT TOP 1 f.[value] FROM sys.fn_trace_getinfo(NULL) f WHERE f.property = 2 )), DEFAULT) T JOIN sys.trace_events TE ON T.EventClass = TE.trace_event_id JOIN sys.trace_subclass_values v ON v.trace_event_id = TE.trace_event_id AND v.subclass_value = t.EventSubClass WHERE te.name IN ( 'Audit Login Failed' )
有大量的事件在Security Audit类,我们这里重点关注“Audit Server Starts and Stops”。
下面的查询将会列出服务器启动事件:
SELECT TE.name AS [EventName] , v.subclass_name , T.DatabaseName , t.DatabaseID , t.NTDomainName , t.ApplicationName , t.LoginName , t.SPID , t.StartTime , t.SessionLoginName FROM sys.fn_trace_gettable(CONVERT(VARCHAR(150), ( SELECT TOP 1 f.[value] FROM sys.fn_trace_getinfo(NULL) f WHERE f.property = 2 )), DEFAULT) T JOIN sys.trace_events TE ON T.EventClass = TE.trace_event_id JOIN sys.trace_subclass_values v ON v.trace_event_id = TE.trace_event_id AND v.subclass_value = t.EventSubClass WHERE te.name IN ( 'Audit Server Starts and Stops' )
请注意:上面的查询只返回Server Start事件,而不会返回Server Stop事件。解释是这样的:像之前所提到的,SQL Server的默认跟踪总共有5个跟踪文件组成,每个文件最大20MB。这五个文件在一些条件下会循环:当实例启动,或当文件大小超过20MB。现在,让我们想一下:我们所列出的查询返回的结果仅来自于当前的跟踪文件,最新的那一个。而因为默认跟踪文件在服务器实例启动时被循环利用,意思是包含Server Stop的事件将保留在前一个默认跟踪文件中。简而言之,在SQL服务重启后,我们当前的默认跟踪文件将有Server Start事件作为第一行。如果你真的希望知道你的SQL Server实例什么时候停止的,你需要至少包含上一个文件的内容,但事实上我们能包含其他4个默认跟踪文件的内容到我们的结果集。我们可以通过调用sys.fn_trace_gettable的方法,他能追加所有的默认跟踪文件。这个函数接受2个参数—文件的位置和名称、以及文件的数量;如果我们传入最旧的默认跟踪文件的路径和名称到第1个参数,而sys.fn_trace_gettable会追加最新的,一旦我们设置了适当的值给第2个参数(文件数量)。如果传入最新的文件给这个函数,那么旧的文件不会被追加。因为文件名包含了文件创建时候的索引,因此很容易计算最旧文件的名称。
找到默认跟踪文件的真实路径,你需要执行一下语句:
SELECT REVERSE(SUBSTRING(REVERSE(path), CHARINDEX('\', REVERSE(path)), 256)) AS DefaultTraceLocation FROM sys.traces WHERE is_default = 1
Server事件
最后一个事件类:Server类。它只包含了一个事件—Server Memory Change。
下面的查询显示了什么时候内存使用改变:
SELECT TE.name AS [EventName] , v.subclass_name , t.IsSystem FROM sys.fn_trace_gettable(CONVERT(VARCHAR(150), ( SELECT TOP 1 f.[value] FROM sys.fn_trace_getinfo(NULL) f WHERE f.property = 2 )), DEFAULT) T JOIN sys.trace_events TE ON T.EventClass = TE.trace_event_id JOIN sys.trace_subclass_values v ON v.trace_event_id = TE.trace_event_id AND v.subclass_value = t.EventSubClass WHERE te.name IN ( 'Server Memory Change' )
该事件子类表明了是否内存增加或减少。
总结
默认跟踪是一个检查SQL Server实例的健康和安全的有效方法。有些陷阱需要记住—主要是文件循环和大小限制,但是修改这一块不是不可能。重要的是,上面的查询语句是从当前最新的默认跟踪文件中获取的结果。依赖于SQL Server实例的繁忙程度,有可能文件循环太快,而DBA无法捕获所有重要的事件;因此,自动化是需要的。