簡體   English   中英

將 XML 數據分解為 SQL 服務器數據庫列的最佳方法

[英]The Best Way to shred XML data into SQL Server database columns

將 XML 數據分解為各種數據庫列的最佳方法是什么? 到目前為止,我主要使用節點和值函數,如下所示:

INSERT INTO some_table (column1, column2, column3)
SELECT
Rows.n.value('(@column1)[1]', 'varchar(20)'),
Rows.n.value('(@column2)[1]', 'nvarchar(100)'),
Rows.n.value('(@column3)[1]', 'int'),
FROM @xml.nodes('//Rows') Rows(n)

但是,我發現即使是中等大小的 xml 數據,這也會變得非常緩慢。

在遇到非常相似的問題時偶然發現了這個問題,我一直在運行一個查詢,處理一個 7.5MB XML 文件(約 10,000 個節點)大約 3.5~4 小時,然后最終放棄。

但是,經過更多研究后,我發現使用模式鍵入 XML 並創建 XML 索引(我將批量插入表中)相同的查詢在約 0.04 毫秒內完成。

這對性能改進有什么好處!

創建模式的代碼:

IF EXISTS ( SELECT * FROM sys.xml_schema_collections where [name] = 'MyXmlSchema')
DROP XML SCHEMA COLLECTION [MyXmlSchema]
GO

DECLARE @MySchema XML
SET @MySchema = 
(
    SELECT * FROM OPENROWSET
    (
        BULK 'C:\Path\To\Schema\MySchema.xsd', SINGLE_CLOB 
    ) AS xmlData
)

CREATE XML SCHEMA COLLECTION [MyXmlSchema] AS @MySchema 
GO

使用鍵入的 XML 列創建表的代碼:

CREATE TABLE [dbo].[XmlFiles] (
    [Id] [uniqueidentifier] NOT NULL,

    -- Data from CV element 
    [Data] xml(CONTENT dbo.[MyXmlSchema]) NOT NULL,

CONSTRAINT [PK_XmlFiles] PRIMARY KEY NONCLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

創建索引的代碼

CREATE PRIMARY XML INDEX PXML_Data
ON [dbo].[XmlFiles] (Data)

不過,有幾件事要記住。 SQL 服務器的 Schema 實現不支持 xsd:include。 這意味着,如果您有一個引用其他架構的架構,您必須將所有這些復制到一個架構中並添加它。

我也會得到一個錯誤:

XQuery [dbo.XmlFiles.Data.value()]: Cannot implicitly atomize or apply 'fn:data()' to complex content elements, found type 'xs:anyType' within inferred type 'element({http://www.mynamespace.fake/schemas}:SequenceNumber,xs:anyType) ?'.

如果我試圖導航到我使用節點 function 選擇的節點上方。 例如

SELECT
    ,C.value('CVElementId[1]', 'INT') AS [CVElementId]
    ,C.value('../SequenceNumber[1]', 'INT') AS [Level]
FROM 
    [dbo].[XmlFiles]
CROSS APPLY
    [Data].nodes('/CVSet/Level/CVElement') AS T(C)

發現處理此問題的最佳方法是使用 OUTER APPLY 在 XML 上執行“外部連接”。

SELECT
    ,C.value('CVElementId[1]', 'INT') AS [CVElementId]
    ,B.value('SequenceNumber[1]', 'INT') AS [Level]
FROM 
    [dbo].[XmlFiles]
CROSS APPLY
    [Data].nodes('/CVSet/Level') AS T(B)
OUTER APPLY
    B.nodes ('CVElement') AS S(C)

希望這對某人有所幫助,因為那幾乎是我的一天。

就我而言,我正在運行 SQL 2005 SP2 (9.0)。

唯一有幫助的是添加 OPTION ( OPTIMIZE FOR (@your_xml_var = NULL ) )。 解釋在下面的鏈接上。

例子:

INSERT INTO @tbl (Tbl_ID, Name, Value, ParamData)
SELECT     1,
    tbl.cols.value('name[1]', 'nvarchar(255)'),
    tbl.cols.value('value[1]', 'nvarchar(255)'),
    tbl.cols.query('./paramdata[1]')
FROM @xml.nodes('//root') as tbl(cols) OPTION ( OPTIMIZE FOR ( @xml = NULL ) )

https://connect.microsoft.com/SQLServer/feedback/details/562092/an-insert-statement-using-xml-nodes-is-very-very-very-slow-in-sql2008-sp1

我不確定什么是最好的方法。 我使用了 OPENXML 構造:

INSERT INTO Test
SELECT Id, Data 
FROM OPENXML (@XmlDocument, '/Root/blah',2)
WITH (Id   int         '@ID',
      Data varchar(10) '@DATA')

為了加快速度,您可以創建 XML 索引。 您可以專門為function 性能優化設置索引。 您也可以使用類型為 xml 的列,它的性能更好。

我們在這里遇到了類似的問題。 我們的 DBA(SP,你這個人)查看了我的代碼,對語法進行了一些調整,我們得到了我們期待的速度。 這很不尋常,因為我的 select 來自 XML 的速度非常快,但插入速度卻很慢。 所以試試這個語法:

INSERT INTO some_table (column1, column2, column3)
    SELECT 
        Rows.n.value(N'(@column1/text())[1]', 'varchar(20)'), 
        Rows.n.value(N'(@column2/text())[1]', 'nvarchar(100)'), 
        Rows.n.value(N'(@column3/text())[1]', 'int')
    FROM @xml.nodes('//Rows') Rows(n) 

因此,指定 text() 參數似乎確實會對性能產生影響。 將我們插入的 2K 行從“我一定寫錯了 - 讓我停下來”到大約 3 秒。 這比我們通過連接運行的原始插入語句快 2 倍。

我不會聲稱這是“最佳”解決方案,但我已經為此目的編寫了一個通用的 SQL CLR 過程 - 它采用“表格” Xml 結構(例如由 FOR Z3501BB093D363810B6710 返回的結構)和 5 個輸出 B93810B67RAW0 .

它不需要對 Xml 中的“表”結構進行任何定制/了解,結果證明它非常快速/高效(盡管這不是設計目標)。 我剛剛在 20 秒內粉碎了一個 25MB(未鍵入)的 xml 變量,返回了 25,000 行相當寬的表格。

希望這可以幫助某人: http://architectshack.com/ClrXmlShredder.ashx

這不是答案,而是對這個問題的補充——我剛剛遇到了同樣的問題,我可以按照 edg 在評論中的要求給出數字。

我的測試有 xml 導致插入 244 條記錄 - 所以 244 個節點。

我正在重寫的代碼平均需要 0.4 秒才能運行。(運行 10 個測試,范圍從 56 秒到 344 秒) 性能不是代碼被重寫的主要原因,但新代碼也需要執行或更好。 這段舊代碼循環 xml 節點,調用一個 sp 每個循環插入一次

新代碼幾乎只是一個 sp; 將 xml 傳入; 切碎它。

使用新代碼進行的測試顯示新的 sp 平均需要 3.7 秒 - 幾乎慢了 10 倍。

我的查詢是在這個問題中發布的表格;

INSERT INTO some_table (column1, column2, column3)
SELECT
Rows.n.value('(@column1)[1]', 'varchar(20)'),
Rows.n.value('(@column2)[1]', 'nvarchar(100)'),
Rows.n.value('(@column3)[1]', 'int'),
FROM @xml.nodes('//Rows') Rows(n)

執行計划似乎顯示,對於每一列,sql 服務器正在執行一個單獨的“表值 Function [XMLReader]”,返回所有 244 行,並通過嵌套循環(內連接)加入所有備份。 因此,在我從大約 30 列中切碎/插入到大約 30 列的情況下,這似乎分別發生了 30 次。

我將不得不轉儲這段代碼,我認為任何優化都不會克服這種固有的緩慢方法。 我將嘗試 sp_xml_preparedocument/OPENXML 方法,看看性能是否更好。 如果有人從 web 搜索中遇到這個問題(就像我一樣),我強烈建議您在 SQL 服務器中使用這種類型的粉碎之前進行一些性能測試

有一個XML 批量加載COM object ( .NET 示例

來自MSDN

您可以使用 INSERT 語句和 OPENXML function 將 XML 數據插入 SQL 服務器數據庫; 但是,當您需要插入大量 XML 數據時,批量加載實用程序可提供更好的性能。

My current solution for large XML sets (> 500 nodes) is to use SQL Bulk Copy (System.Data.SqlClient.SqlBulkCopy) by using a DataSet to load the XML into memory and then pass the table to SqlBulkCopy (defining a XML schema helps )。

顯然存在一個陷阱,例如不必要地使用 DataSet 並首先將整個文檔加載到 memory 中。 我想在未來進一步 go 並實現我自己的 IDataReader 以繞過 DataSet 方法,但目前 DataSet 對於這項工作來說“足夠好”。

基本上,我從來沒有找到解決我最初的問題的方法,即這種類型的 XML 切碎的性能緩慢。 由於鍵入的 xml 查詢本身很慢或與事務和 SQL 服務器日志有關,因此它可能會很慢。 我猜想鍵入的 xml 函數從未設計用於在非平凡節點大小上運行。

XML Bulk Load: I tried this and it was fast but I had trouble getting the COM dll to work under 64bit environments and I generally try to avoid COM dlls that no longer appear to be supported.

sp_xml_preparedocument/OPENXML:我從來沒有走這條路,所以有興趣看看它的表現如何。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM