SQL 使用 sys.fn_xe_file_target_read_file 导入扩展事件文件如何仅获取自上次导入以来的值

SQL importing Extended Events file using sys.fn_xe_file_target_read_file how to only get values since last import

我正在使用 SQL Server 2012

我有一个很长的 运行 扩展事件(运行几天以捕获事件)保存到 .xel 文件。

我有一个定期运行的作业,用于将数据导入暂存 table。 我只从文件中导入 XML event_data 列,这样我就可以解析出我需要的 XML 字段并保存到 table 以进行报告。

我知道我上次 运行 导入是什么时候所以我想看看我是否只能 select 文件中自上次导入过程以来添加的记录 运行.

我现在可以使用它了,但是它将文件中的所有记录导入暂存 tables,解析出我需要的字段(包括时间戳),然后仅导入自最后工作 运行.

我的进程只插入自上次作业以来的新记录 运行 所以这一切都很好,但是它为文件中的所有记录导入和解析 XML 做了很多工作,包括我上次导入作业 运行 的那些。

所以我想找到一种方法,如果文件已经导入,则根本不从文件导入,或者至少不必为已经导入的记录解析 XML(尽管我有现在解析它以获取时间戳以排除已处理的时间戳)。

下面是我所拥有的,正如我所说,它可以工作,但是如果我能找到一种方法来跳过我已经导入的那些,我需要做很多额外的工作。

我只包含了我需要帮助的流程步骤:

-- pull data from file path and insert into staging table
INSERT INTO #CaptureObjectUsageFileData (event_data)
SELECT cast(event_data as XML) as event_data
FROM sys.fn_xe_file_target_read_file(@FilePathNameToImport, null, null, null)


-- parse out the data needed (only columns using) and insert into temp table for parsed data
INSERT INTO #CaptureObjectUsageEventData (EventTime, EventObjectType, EventObjectName)
SELECT n.value('(@timestamp)[1]', 'datetime') AS [utc_timestamp],
n.value('(data[@name="object_type"]/text)[1]', 'varchar(500)') AS ObjectType,
n.value('(data[@name="object_name"]/value)[1]', 'varchar(500)') as ObjectName
from (
    SELECT event_data
    FROM #CaptureObjectUsageFileData (NOLOCK)
) ed
CROSS apply ed.event_data.nodes('event') as q(n)


-- select from temp table as another step for speed/conversion
--  converting the timestamp to smalldatetime so it doesnt get miliseconds so when we select distinct it wont have lots of dupes
INSERT INTO DBALocal.dbo.DBObjectUsageTracking(DatabaseID, ObjectType, ObjectName, ObjectUsageDateTime)
SELECT DISTINCT @DBID, EventObjectType, EventObjectName, CAST(EventTime AS SMALLDATETIME)
FROM #CaptureObjectUsageEventData
WHERE EventTime > @LastRunDateTime

好的,我已经发表了评论,但是 - 在更深入地思考并查看您的代码之后 - 这可能相当简单:

您可以存储上次导入的时间并在 .nodes() 中使用 谓词 (就像您在 .value() 中这样做以获得正确的 <data>-元素).

尝试这样的事情:

DECLARE @LastImport DATETIME=GETDATE(); --put the last import's time here

and then

CROSS apply ed.event_data.nodes('event[@timestamp cast as xs:dateTime? > sql:variable("@LastImport")]') as q(n)

这样做,.nodes() 应该 return 只有 <event> 个元素,其中条件已满足。如果这没有帮助,请显示一些 XML 的简化示例以及您想要得到的内容。

接受了上面的答案,但发布了我有问题的部分的代码,并完整地发布了 comments/fixes 我所做的更新(同样不是完整的代码)但重要的部分。使用@Shnugo 的帮助,我能够从我的进程中完全删除一个临时 table,我需要在插入到我的永久 table 之前进行日期过滤,有了他的回答,我可以直接插入到永久 table。在我测试的小数据集中,更新和删除额外代码将 运行ning 时间减少了 1/3。随着我获得的数据越多,此改进将产生的影响越大。

这是为了 运行 长时间的扩展事件会话而设计的。 它会告诉我正在使用哪些对象(稍后查询系统 tables)告诉我哪些对象没有被使用。 请参阅下面的扩展事件生成代码: 我正在抓取以下信息:sp_statement_starting 并且只抓取 SP 和函数事件并且只保存对象名称、类型和时间戳 我没有保存 SQL 文本,因为我不需要它。

sp_statement_starting 将每个语句拉入存储过程,因此当 SP 运行 时,它可能有 1-100 个语句开始事件, 并将那么多记录插入文件(这比我的目的所需的数据多得多)。

在我将文件导入暂存区后的代码中 table 我将时间戳缩短为 shortdatetime 并从文件中的所有记录中选择不同的值

我这样做是因为它为 SP 中的每个语句插入一条记录,将数据缩短为 shortdatetime 并选择 distinct 大大减少了插入记录的数量。

我知道我可以只保留对象名称并只插入唯一值并完全忽略时间,但我想大致了解它们被调用的频率。

CREATE EVENT SESSION [CaptureObjectUsage_SubmissionEngine] ON SERVER 
ADD EVENT sqlserver.sp_statement_starting(
    -- collect object name but NOT statement, thats not needed
    SET collect_object_name=(1),
    collect_statement=(0)
    WHERE (
    -- this is for functions or SP's
        (
            -- functions
            [object_type]=(8272) 
            -- SProcs
            OR [object_type]=(20038)
        ) 
        AND [sqlserver].[database_name]=N'DBNAMEHERE' 
        AND [sqlserver].[is_system]=(0))
    ) 
ADD TARGET package0.event_file( 
    SET filename=N'c:\Path\CaptureObjectUsage.xel'  -- mine that was default UI gave me
)
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




-- ***************************************************************************
--      code for importing
-- ***************************************************************************

-- pull data from file path and insert into staging table
INSERT INTO #CaptureObjectUsageFileData (event_data)
SELECT cast(event_data as XML) as event_data
FROM sys.fn_xe_file_target_read_file(@FilePathNameToImport, null, null, null)


-- with the XML.nodes parsing I can insert directly into my final table because it does the logic here
INSERT INTO DBALocal.dbo.DBObjectUsageTracking(DatabaseID, ObjectType, ObjectName, ObjectUsageDateTime)
SELECT DISTINCT @DBID, -- @DBID is variable I set above so I dont need to use DBNAME and take up a ton more space
n.value('(data[@name="object_type"]/text)[1]', 'varchar(500)') AS ObjectType,
n.value('(data[@name="object_name"]/value)[1]', 'varchar(500)') as ObjectName,
CAST(n.value('(@timestamp)[1]', 'datetime') AS SMALLDATETIME) AS [utc_timestamp]
from (
    SELECT event_data
    FROM #CaptureObjectUsageFileData (NOLOCK)
) ed
-- original  before adding the .node logic
--CROSS apply ed.event_data.nodes('event') as q(n)
-- updated to reduce amount of data to import
CROSS apply ed.event_data.nodes('event[@timestamp cast as xs:dateTime? > sql:variable("@LastRunDateTime")]') as q(n)

老问题,但由于没有人提供使用 sys.fn_xe_file_target_read_fileinitial_offset 参数的解决方案,我将放弃一些关于我几年前如何使用它的代码。我认为这不是一个有效的解决方案,因为我从一个更大的代码库中剪切并粘贴了它,但它显示了让它工作所需的一切。

-- table to hold the config, i.e. the last file read and the offset.
IF OBJECT_ID('session_data_reader_config', 'U') IS NULL 
CREATE TABLE session_data_reader_config
(
    lock                  bit           PRIMARY KEY 
                                        DEFAULT 1 
                                        CHECK(lock=1) -- to allow only one record in the table
    , file_target_path      nvarchar(260)
    , last_file_read        nvarchar(260)
    , last_file_read_offset bigint
    , file_exists           AS dbo.fn_file_exists(last_file_read)
)


-- Insert the default value to start reading the log files, if no values are already present.
IF NOT EXISTS(SELECT 1 FROM  session_data_reader_config )
INSERT INTO session_data_reader_config (file_target_path,last_file_read,last_file_read_offset)
VALUES ('PathToYourFiles*.xel',NULL,NULL)


-- import the EE data into the staging table
IF EXISTS(SELECT 1 FROM [session_data_reader_config] WHERE file_exists = 1 )
BEGIN
    INSERT INTO [staging_table] ([file_name], [file_offset], [data])
    SELECT t2.file_name, t2.file_offset, t2.event_data --, CAST(t2.event_data as XML)
    FROM [session_data_reader_config]
    CROSS APPLY sys.fn_xe_file_target_read_file(file_target_path,NULL, last_file_read, last_file_read_offset) t2
END 
ELSE
BEGIN
    INSERT INTO [staging_table] ([file_name], [file_offset], [data])
    SELECT t2.file_name, t2.file_offset, t2.event_data
    FROM [session_data_reader_config]
    CROSS APPLY sys.fn_xe_file_target_read_file(file_target_path,NULL, NULL, NULL) t2
END


-- update the config table with the last file and offset
UPDATE [session_data_reader_config]
    SET [last_file_read]        = T.[file_name]
      , [last_file_read_offset] = T.[file_offset]
    FROM (
            SELECT TOP (1) 
                   [file_name]
                 , [file_offset]
              FROM [staging_table]
          ORDER BY [id] DESC 
    ) AS T ([file_name], [file_offset])