【问题标题】:The Best Way to shred XML data into SQL Server database columns将 XML 数据分解为 SQL Server 数据库列的最佳方法
【发布时间】:2008-09-14 09:39:29
【问题描述】:

将 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 数据,这也会变得非常慢。

【问题讨论】:

  • 也许如果你用实际数字量化“中等大小”和“慢”,那么人们会更好地提供建议?
  • 中等 > 300 - 500 个节点一次

标签: sql-server xml


【解决方案1】:

在遇到非常相似的问题时偶然发现了这个问题,在最终放弃之前,我一直在运行一个处理 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 Server 的 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) ?'.

如果我尝试在我使用节点功能选择的节点上方导航。例如

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)

希望这对某人有所帮助,因为那几乎是我的一天。

【讨论】:

  • 很好的答案 Dan,您能否详细说明如何将 xml 节点映射到表中的列?
  • 嘿 longhairedsi,我不确定我是否正确理解了您的问题。您是指将 XML 文档的特定部分插入到表格列中,还是像我上面所说的将整个文档插入到单个列中,然后使用 select 语句以及 .nodes 和 .value 函数(以及 xpath 语法)来获取数据以表格格式输出?
  • 我想我可能已经理解了您的回答 :) 我以为您是在将 xml 作为临时位置添加到列中,然后再切碎到现有表列(例如 INSERT INTO xxx FROM XmlFiles.Data)等像。我一直在寻找将 xml 共享到现有列的最高效的方法,我认为只需做一些工作,您的解决方案就可以很好地完成这项工作。
  • 遗憾的是,如果您使用 Azure 数据库,它们似乎不支持 XML 索引。 (msdn.microsoft.com/en-us/library/windowsazure/ff394115.aspx '类型化 XML 和 XML 索引不受支持。')
  • Azure 现在支持 XML 索引。我相信它是在 2015 年底或 2016 年初添加的,但“何时”并不重要。我真的只是不希望这里的“最后一句话”是它没有!
【解决方案2】:

就我而言,我正在运行 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

【讨论】:

    【解决方案3】:

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

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

    为了加快速度,您可以创建 XML 索引。您可以专门为 value 功能性能优化设置索引。您也可以使用类型化的 xml 列,它的性能更好。

    【讨论】:

      【解决方案4】:

      我们在这里遇到了类似的问题。我们的 DBA(SP,你这个人)查看了我的代码,对语法进行了一些调整,我们得到了我们期待的速度。这很不寻常,因为我从 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 倍。

      【讨论】:

        【解决方案5】:

        我不会声称这是“最佳”解决方案,但我已经为此目的编写了一个通用 SQL CLR 过程 - 它采用“表格”Xml 结构(例如 FOR XML RAW 返回的结构)和输出结果集。

        它不需要任何自定义/了解 Xml 中“表”的结构,并且结果非常快速/高效(尽管这不是设计目标)。我刚刚在 20 秒内粉碎了一个 25MB(无类型)的 xml 变量,返回了一个相当宽的表的 25,000 行。

        希望这可以帮助某人: http://architectshack.com/ClrXmlShredder.ashx

        【讨论】:

          【解决方案6】:

          这不是答案,更多的是对这个问题的补充 - 我刚刚遇到了同样的问题,我可以按照 edg 在评论中的要求给出数字。

          我的测试有 xml,导致插入 244 条记录 - 所以 244 个节点。

          我正在重写的代码平均需要 0.4 秒才能运行。(运行 10 个测试,从 0.56 秒到 0.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 server 正在执行一个单独的“表值函数 [XMLReader]”,返回所有 244 行,并通过嵌套循环(内连接)连接所有备份。因此,在我从大约 30 列中切碎/插入的情况下,这似乎分别发生了 30 次。

          我将不得不转储这段代码,我认为任何优化都无法克服这种固有缓慢的方法。我将尝试 sp_xml_preparedocument/OPENXML 方法,看看性能是否更好。如果有人从网络搜索中遇到这个问题(就像我一样),我强烈建议您在 SQL Server 中使用这种类型的粉碎之前进行一些性能测试

          【讨论】:

          • 这里有有趣的信息,但它被埋没了。如果您还在犹豫,请将其作为一个新问题发布(如果您找到了好的解决方案或发现了问题,请自行回答:-)
          • 这本身不是一个答案,而是对原始问题的肯定。请发布您自己的问题,而不是通过对原始发布者问题的评论链接到您的问题。
          • @pst 嗨,是的,还在。谢谢,这是我需要的,所以我不需要重新发布它。
          • @jpierson 我发布了这个作为答案,以帮助任何通过这种方式的人提供一些更详细的信息 - 这就是答案:“我认为任何优化都不会克服这种固有的方法慢”我无法在评论中发布这么多信息。这是我两年前做的工作,抱歉,我不会转发它
          【解决方案7】:

          有一个XML Bulk load COM 对象(.NET Example

          来自MSDN

          您可以将 XML 数据插入 SQL 使用 INSERT 的服务器数据库 语句和 OPENXML 函数; 但是,批量加载实用程序 当您提供更好的性能 需要插入大量 XML 数据。

          【讨论】:

          • 我一直在走这条路,我不建议这样做。我们最大的抱怨是 XML Bulk Load 在事务上下文中表现不佳。我们花了太多时间试图让它工作,最后,这是不值得的 COM 部分。
          【解决方案8】:

          我目前针对大型 XML 集(> 500 个节点)的解决方案是使用 SQL Bulk Copy (System.Data.SqlClient.SqlBulkCopy) 通过使用 DataSet 将 XML 加载到内存中,然后将表传递给 SqlBulkCopy(定义一个XML 模式有帮助)。

          显然存在一些陷阱,例如不必要地使用 DataSet 并首先将整个文档加载到内存中。我想在未来走得更远,实现我自己的 IDataReader 以绕过 DataSet 方法,但目前 DataSet 对于这项工作来说“足够好”。

          基本上,我从来没有找到解决我最初的问题的方法,即这种类型的 XML 粉碎性能缓慢。由于键入的 xml 查询本身很慢,或者与事务和 SQL Server 日志有关,它可能会很慢。我猜想这些类型化的 xml 函数从来都不是为在非平凡节点大小上运行而设计的。

          XML 批量加载:我试过了,它速度很快,但我无法让 COM dll 在 64 位环境下工作,我通常会尽量避免使用似乎不再受支持的 COM dll。

          sp_xml_preparedocument/OPENXML:我从来没有走这条路,所以有兴趣看看它的表现。

          【讨论】:

          • 我认为最初问题的最可能原因是缺少索引。如果不是直接查询那个 xml 片段,而是将它放在临时表中的 xml 类型列中,在该列上定义一个索引然后查询它,它可能会产生很大的不同。
          猜你喜欢
          • 2012-08-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-10-16
          • 2016-01-15
          • 1970-01-01
          • 1970-01-01
          • 2010-09-18
          相关资源
          最近更新 更多