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_file
的 initial_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])
我正在使用 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_file
的 initial_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])