閱讀446 返回首頁    go 阿裏雲 go 技術社區[雲棲]


MSSQL· 實現分析 · Extend Event日誌文件的分析方法

背景

在前兩篇月報分享中,6月份月報我們分享了SQL Server實現審計日誌功能的方法探索,最終從可靠性、對象級別、可維護性、開銷和對數據庫係統影響五個方麵得出最佳選項Extend Event;7月份月報我們量化分析了使用Extend Event實現審計日誌功能對SQL Server本身的性能和吞吐量的影響,結論是對係統性能和吞吐量影響均在0.01%左右;8月份的月報分享是SQL Server審計日誌專題的最後一期,探討Extend Event實現審計日誌功能的分析方法匯總,以及這些方法的優缺點。

6月份月報,詳情請戳:MSSQL · 實現分析 · SQL Server實現審計日誌的方案探索

7月份月報,詳情請戳:MSSQL · 實現分析 · Extend Event實現審計日誌對SQL Server性能影響

問題引入

為了兼容SQL Server 2008R2版本,我們稍微對實現審計日誌功能的擴展事件創建方法稍微修改如下:

USE master
GO

CREATE EVENT SESSION [svrXEvent_User_Define_Testing] ON SERVER 
ADD EVENT sqlserver.sql_statement_completed
( 
	ACTION 
	( 
		sqlserver.database_id,
		sqlserver.session_id, 
		sqlserver.username, 
		sqlserver.client_hostname,
		sqlserver.client_app_name,
		sqlserver.sql_text, 
		sqlserver.plan_handle,
		sqlserver.tsql_stack,
		sqlserver.is_system,
		package0.collect_system_time
	) 
	WHERE sqlserver.username <> N'NT AUTHORITY\SYSTEM'
		AND sqlserver.username <> 'sa'
		AND sqlserver.is_system = 0		
)
ADD TARGET package0.asynchronous_file_target
( 
	SET 
		FILENAME = N'C:\Temp\svrXEvent_User_Define_Testing.xel', 
		METADATAFILE = N'C:\Temp\svrXEvent_User_Define_Testing.xem',
		MAX_FILE_SIZE = 10,
		MAX_ROLLOVER_FILES = 500
)
WITH (
	EVENT_RETENTION_MODE = NO_EVENT_LOSS,
	MAX_DISPATCH_LATENCY = 5 SECONDS,
    STARTUP_STATE=ON
);
GO


-- We need to enable event session to capture event and event data 
ALTER EVENT SESSION [svrXEvent_User_Define_Testing]
ON SERVER STATE = START;
GO

擴展事件創建完畢並啟動以後,發生在SQL Server數據庫服務端的所有sql_statement_completed事件信息都會被擴展事件異步滾動記錄在日誌文件svrXEvent_User_Define_Testing.xel文件中,日誌文件格式是svrXEvent_User_Define_Testing_0_TimeStamp.xel,比如svrXEvent_User_Define_Testing_0_131465070445690000.xel。這裏就引入了這期月報分享的重點問題了:

審計日誌有哪些分析方法

這些方法各自的優缺點是什麼

我們該如何選擇哪種適用的方法

使用DMF

SQL Server擴展事件(Extend Event,簡稱為XE)采用異步的方式將審計日誌記錄寫入目標日誌文件中,且每個事件以XML格式單行寫入日誌文件,因此我們可以采用SQL Server提供的動態管理函數sys.fn_xe_file_target_read_file來讀取和分析升級日誌文件。

全量讀取

全量審計日誌讀取是指使用SQL Server DMF sys.fn_xe_file_target_read_file ( path, mdpath, initial_file_name, initial_offset ) 中,不傳入initial_file_name和initial_offset。這種方法讀取的是審計日誌目錄下所有的審計日誌文件中的內容。比如,以下是使用DMF全量讀取所有審計日誌文件記錄的例子:

USE master
GO

SELECT *
FROM sys.fn_xe_file_target_read_file('C:\Temp\svrXEvent_User_Define_Testing*.xel', 
		'C:\Temp\svrXEvent_User_Define_Testing*.xem', null, null)

展示的結果如下:
01.png

從這個結果來看,我們無法明確的知道哪個用戶在哪個時間點執行了哪些SQL語句,執行耗時多少等更為詳細有價值的信息。這裏我們需要采用XML解析的方法來分析Event_data字段中更為豐富的內容。請使用下麵的查詢語句獲取更為詳細的信息:

-- This is SQL 2008R2
;WITH events_cte
AS (
	SELECT
		[event_data] = T.C.query('.'),
		[event_name] = T.C.value('(event/@name)[1]','varchar(100)'),
		[event_time] = DATEADD(mi, DATEDIFF(mi, GETUTCDATE(), CURRENT_TIMESTAMP),T.C.value('(event/@timestamp)[1]','datetime2')),
		[client app name] = T.C.value('(event/action[@name="client_app_name"]/value/text())[1]', 'sysname'),
		[client host name] = T.C.value('(event/action[@name="client_hostname"]/value/text())[1]', 'sysname'),
		[database_id]= T.C.value('(event/action[@name="database_id"]/value/text())[1]', 'int'),
		[cpu time (ms)] = T.C.value('(event/data[@name="cpu"]/value/text())[1]', 'bigint'),
		[logical reads] = T.C.value('(event/data[@name="reads"]/value/text())[1]', 'bigint'),
		[logical writes] = T.C.value('(event/data[@name="writes"]/value/text())[1]', 'bigint'),
		[duration (ms)] = T.C.value('(event/data[@name="duration"]/value/text())[1]', 'bigint'),
		[row count] = T.C.value('(event/data[@name="row_count"]/value/text())[1]', 'bigint'),
		[sql_text] = T.C.value('(event/action[@name="sql_text"]/value/text())[1]','nvarchar(max)'),
		[session_id] = T.C.value('(event/action[@name="session_id"]/value/text())[1]','int'),
		[user_name] = T.C.value('(event/action[@name="username"]/value/text())[1]','sysname'),
		[is_system] = T.C.value('(event/action[@name="is_system"]/value/text())[1]','sysname'),
		[query_timestamp] = T.C.value('(event/action[@name="collect_system_time"]/value/text())[1]','bigint'),
		[query_time] = DATEADD(mi, DATEDIFF(mi, GETUTCDATE(), CURRENT_TIMESTAMP),T.C.value('(event/action[@name="collect_system_time"]/text/text())[1]','datetime2'))
	FROM sys.fn_xe_file_target_read_file('C:\Temp\svrXEvent_User_Define_Testing*.xel', 
		'C:\Temp\svrXEvent_User_Define_Testing*.xem', null, null)
		CROSS APPLY (SELECT CAST(event_data as XML) AS event_data) as T(C)
)
SELECT 
	
	cte.session_id,
	--cte.query_timestamp,
	--cte.[event_time],
	cte.[query_time],
	--cte.[event_name],
	cte.user_name,
	[database_name] = db.name,
	cte.[database_id],
	cte.[client host name],
	
	cte.[logical reads],
	cte.[logical writes],
	cte.[cpu time (ms)],
	cte.[duration (ms)],
	--cte.[plan_handle],
	cte.sql_text,
	sql_text_hash = CHECKSUM(cte.sql_text),
	cte.[client app name],
	cte.[event_data],
	cte.is_system
FROM events_cte as cte
	LEFT JOIN sys.databases as db
	on cte.database_id = db.database_id
ORDER BY [query_time] ASC
;

執行結果展示如下:
02.png

從這個結果集中,我們可以很清楚的知道每一條SQL語句執行的詳細情況,包括:用戶名、執行時間點、客戶機名、邏輯讀、邏輯寫、CPU消耗、執行時間消耗、查詢語句詳情等非常重要的信息。

部分讀取

使用DMF sys.fn_xe_file_target_read_file ( path, mdpath, initial_file_name, initial_offset )實現審計日誌除了全量讀取外,還可以實現部分讀取,我可以傳入initial_file_name和initial_offset來實現從某個日誌文件的特定offset(文件內容偏移量)開始讀取。以此來減小每次讀取審計日誌文件的大小。比如,我們從文件C:\Temp\svrXEvent_User_Define_Testing_0_131471065758970000.xel中的偏移量為開始94720開始讀取,執行方法如下:

USE master
GO

SELECT *
FROM sys.fn_xe_file_target_read_file('C:\Temp\svrXEvent_User_Define_Testing*.xel', 
		'C:\Temp\svrXEvent_User_Define_Testing*.xem', 'C:\Temp\svrXEvent_User_Define_Testing_0_131471065758970000.xel', 94720)

執行結果截圖如下:
03.png

當然,你也可以使用“全量讀取”中的分析方法來獲取部分讀取到的審計日誌詳細信息,在此不再累述。

XEReader API

我們除了使用SQL Server本身提高的DMF來分析審計日誌以外,還可以使用XE Reader API,通過編程的方式來讀取審計日誌文件。從SQL Server 2012開始,在Shared(C:\Program Files\Microsoft SQL Server\110\Shared)目錄下,提供了XEvent相關的兩個dll文件,可以使用XEReader的API接口來讀取審計日誌文件。
SQL 2012: Microsoft.SqlServer.XEvent.Linq.dll
SQL 2014: Microsoft.SqlServer.XEvent.Linq.dll和Microsoft.SqlServer.XE.Core.dll
SQL 2016: Microsoft.SqlServer.XEvent.Linq.dll和Microsoft.SqlServer.XE.Core.dll
以下是Visual Studio 2015編程工具,使用C#編程語言,編寫的控製台應用程序項目,來詳細看看如何使用XEReader API來實現分析審計日誌文件。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.SqlServer.XEvent.Linq;

namespace MonthlyShareExtendEventDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            string[] xelFiles = new string[] { @"C:\Temp\svrXEvent_User_Define_Testing*.xel" };
            string[] xemFiles = new string[] { @"C:\Temp\svrXEvent_User_Define_Testing*.xem" };
            QueryableXEventData events = new QueryableXEventData(xelFiles, xemFiles);
            foreach (PublishedEvent evt in events)
            {
                Console.WriteLine("=>>>>>>>>>>>>>>>>>>" + evt.Name);

                foreach (PublishedEventField fld in evt.Fields)
                {
                    Console.WriteLine("\tField: {0} = {1}", fld.Name, fld.Value);
                }

                foreach (PublishedAction act in evt.Actions)
                {
                    Console.WriteLine("\tAction: {0} = {1}", act.Name, act.Value);
                }
                Console.WriteLine("=<<<<<<<<<<<<<<<" + evt.Name);
            }

            Console.ReadKey();

        }
    }
}

我截圖其中一條得到的審計日誌如下圖所示:
04.png

注意:
在使用XEReader API分析審計日誌,需要依賴兩個安裝包:SQLSysClrTypes.msi和SharedManagementObjects.msi,請提前安裝完畢。

事件流讀取

當然我們也可以采用XEReader API事件流的方式讀取審計日誌,當客戶端有查詢語句提交到SQL Server 後台服務,事件流會捕獲到這個查詢行為,並加以分析。事例代碼如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.SqlServer.XEvent.Linq;

namespace XEStreamDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            string connString = string.Format("Data Source=.,{0};Initial Catalog={1};Integrated Security=SSPI", 1433, "master");
            string xeSessionName = @"svrXEvent_User_Define_Testing";
            using (QueryableXEventData eventData = new QueryableXEventData(connString, xeSessionName, EventStreamSourceOptions.EventStream, EventStreamCacheOptions.DoNotCache))
            {
                foreach (PublishedEvent evt in eventData)
                {
                    Console.WriteLine("=>>>>>>>>>>>>>>>>>>" + evt.Name);

                    foreach (PublishedEventField fld in evt.Fields)
                    {
                        Console.WriteLine("\tField: {0} = {1}", fld.Name, fld.Value);
                    }

                    foreach (PublishedAction act in evt.Actions)
                    {
                        Console.WriteLine("\tAction: {0} = {1}", act.Name, act.Value);
                    }
                    Console.WriteLine("=<<<<<<<<<<<<<<<" + evt.Name);
                }
            }

            Console.ReadKey();
        }
    }
}

當執行查詢的時候,這個控製台應用程序會捕獲到SQL Server 服務端執行的查詢語句,如下截圖:
05.png

注意:
基於事件流分析SQL Server審計日誌功能的方法不支持SQL Server 2008以及SQL Server 2008R2版本,最低的版本要求是SQL Server 2012。因為在SQL Server 2012以下版本中會報告“Invalid object name ‘sys.fn_MSxe_read_event_stream”異常信息,錯誤信息如下所示。
An unhandled exception of type ‘System.Data.SqlClient.SqlException’ occurred in System.Data.dll
Additional information: Invalid object name ‘sys.fn_MSxe_read_event_stream’.
異常信息截圖如下:
06.png

三種方法對比

這一章節介紹三種審計日誌分析方法的對比,我們將會從以下幾個角度來衡量這三種方法:

是否依賴SQL Server Service

分析延遲性

穩定性

對SQL Server的影響

DMF

DMF sys.fn_xe_file_target_read_file是SQL Server本身內置的對象,所以使用這種方法分析審計日誌信息,無需過多的編程處理,門檻較低,甚至可以直接使用SSMS都可以分析審計日誌文件。這些是使用DMF分析審計日誌的優點。當然,這個方法的缺點也很明顯:使用DMF方式讀取審計日誌,需要連接到SQL Server服務,所以要求SQL Server服務本身是啟動的,因為這個是使用SQL Server內置的動態管理函數來實現的;而且這種分析方法需要使用SQL Server對XML操作技術來解析event_data,解析XML是一個CPU密集型操作,非常消耗係統CPU資源。在我之前的測試案例中,使用DMF方法分析審計日誌詳情導致了50%多的額外CPU開銷。如下截圖所示:
07.png

XEReader API

使用SQL Server XEReader提供的API讀取審計日誌文件的方法,完全是基於審計日誌文件的操作方式,可以獨立於SQL Server的服務。換句話說,不管SQL Server是處於關閉還是啟動狀態,對我們審計日誌的分析不會受到任何影響。這些是使用XEReader API分析審計日誌的優點。而這個方法也有它的缺點:當我們分析當前(正在被Extend Event Session對象寫入的日誌文件)審計日誌文件時,我們不知道(或者很難知道)哪些記錄是我們分析過的,哪些是還未分析的?如果這個問題解決不了的話,很可能就會導致審計日誌記錄的重複或者丟失。當然,我們也可以采用XE循環寫入審計日誌文件的方法,每次讀取Archive出來的審計日誌文件,跳過當前文件的讀取,等待當前文件寫滿固定大小,Archive出來以後,再來讀取分析。這個改進方法會引入另外一個問題是,可能會導致審計日誌的分析延遲,而且延遲的時間還不確定。比如:用戶查詢在10分鍾後才寫滿當前審計日誌文件,那麼延遲是10分鍾;如果用戶查詢在1個小時之內才寫滿當前審計日誌文件,那麼延遲將是1個小時。

事件流讀取

基於用戶查詢事件流式分析審計日誌的方法,優點也特別明顯:延遲非常小,可以控製在秒級內,實時性表現良好,它解決了XEReader API查詢事件延遲的問題。然而缺點是:也需要依賴SQL Service的啟動狀態,否則會報告異常;在大量查詢瞬間(短時間內)執行的時候(比如用戶不小心寫了一個死循環查詢),重啟SQL Service或者Extend Event Session狀態時,根據我測試的情況來看,這種場景會導致審計日誌記錄丟失,可靠性得不到保證。

最後總結

基於以上三種審計日誌分析方法的優缺點總結來看,我們綜合打分匯總如下:

DMF:對SQL Service有依賴,得分0;延遲取決於Offset的移動效率,得分80;穩定性有保證,得分100;對SQL Server CPU影響較大,得分為0;

XEReader API:對SQL Service無依賴,得分100;延遲取決於查詢產生的速度,得分50;穩定性有保證,得分100;對SQL Server 影響很小,得分為0;

XEReader Stream:對SQL Service有依賴,得分0;延遲非常低,得分100;有不穩定的場景存在,得分50;對SQL Server 影響較小,得分為100;

08.png

將綜合打分做成雷達圖,如下:
09.png

從這個匯總圖來看,XEReader API直接分析審計日誌文件的方法在依賴性,延遲性,穩定性和影響方麵,綜合表現最佳。

參考文章

Introducing the Extended Events Reader

最後更新:2017-08-21 09:03:11

  上一篇:go  MySQL · 源碼分析 · SHUTDOWN過程
  下一篇:go  PgSQL · 源碼分析 · PG 優化器中的pathkey與索引在排序時的使用