【问题标题】:Is there a faster way than this to extract data from XML nodes in T-SQL?有没有比这更快的方法从 T-SQL 中的 XML 节点中提取数据?
【发布时间】:2018-09-18 17:06:22
【问题描述】:

我目前正在尝试在 T-SQL 中创建一个存储过程,它以 XML 表作为输入,然后将其中的数据插入到临时表中。

我使用的 XML 格式如下:

<Table>
    <row MyFirstColumn="foo" MySecondColumn="bar" ... />
</Table>

我用来将此 XML 数据插入临时表的 SQL 格式如下:

INSERT INTO
    #TempTable
SELECT
    T.c.value('@MyFirstColumn', 'varchar(50)')
   ,T.c.value('@MySecondColumn', 'varchar(50)')
   ,...
FROM
    @x.nodes('//Table/row') T(c)

但是,我对包含 150 列和超过 200,000 行的 XML 表执行此操作。目前,对 10,000 行执行此 SQL 大约需要 142 秒,因此这完全不适合处理包含大量行的 XML 表。

谁能建议一种方法来加快这个过程?

【问题讨论】:

  • 创建数据的关系模型,并使用您已经在使用的工具将数据插入到这些表中。然后可以对这些表进行索引、压缩、分区等操作。一旦您完成了这项工作 (ETL),您就可以比查询 XML 文档更快地查询这些表。
  • 尝试将 xml 存储到临时表 CREATE TABLE #tmp (xml_content xml) 中。在临时表上创建 XML 主索引,并查询临时表。这可能比直接查询 xml 更快。
  • SQLXML 批量加载是从 xml 文件导入数据的最快方法之一。 docs.microsoft.com/en-us/sql/relational-databases/…
  • SQLXML 4.0 对象是否不需要 .NET 框架?
  • 我听说过一些事情(当然我在这里的经验有限)可以加快速度。 1. 使用对象而不是表格更快。 2. 对于更大的数据集,使用 OPENXML 更快。 3. 在 VB 脚本中使用带有 SQLXML 批量加载的 ODBC 连接可以很快。我不知道这意味着什么,因为我还没有真正研究过它 4. 我感兴趣的是将部分 xml 加载到对象变量中,因为当您添加节点/行时,它会以指数方式变慢,因此可能会递归加载最佳一次的节点/行数。

标签: sql sql-server xml tsql


【解决方案1】:

当您查询大量列时,在 SQL Server 中使用节点()/值()分解 XML 会出现性能问题。有一个嵌套循环连接,每列调用一个 xml 函数。

三列查询计划:

5列查询计划:

想象一下超过 150 列会是什么样子。

您的另一个选择是使用OPENXML。它在许多列上没有相同的问题。

您的查询将如下所示:

declare @H int;
declare @X xml;

exec sys.sp_xml_preparedocument @H output,
                                @X;

select C1,
       C2,
       C3
from
       openxml(@H, 'Table/row', 0)
       with (
              C1 int,
              C2 int,
              C3 int
            );

exec sys.sp_xml_removedocument @H;

对我来说,使用 150 列和 1000 行使用 nodes()/value() 大约需要 14 秒,使用 OPENXML 大约需要 3 秒。

Vote for a change.

用于测试的代码;

drop table T;

go

declare @C int = 150;
declare @S nvarchar(max);
declare @X xml;
declare @N int = 1000;
declare @D datetime;

set @S = 'create table T('+
stuff((
      select top(@C) ', '+N'C'+cast(row_number() over(order by 1/0) as nvarchar(3)) + N' int'
      from sys.columns
      for xml path('')
      ), 1, 2, '') + ')'

exec sp_executesql @S;

set @S = 'insert into T select top(@N) '+
stuff((
      select top(@C) ',1'
      from sys.columns as c1
      for xml path('')
      ), 1, 1, '') + ' from sys.columns as c1, sys.columns as c2';

exec sp_executesql @S, N'@N int', @N;

set @X = (
         select *
         from dbo.T
         for xml raw, root('Table')
         );

set @S = 'select '+
stuff((
      select top(@C) ', '+N'T.X.value(''@C'+cast(row_number() over(order by 1/0) as nvarchar(3)) + N''', ''int'')'
      from sys.columns
      for xml path('')
      ), 1, 2, '') + ' from @X.nodes(''Table/row'') as T(X)'

set @D = getdate();
exec sp_executesql @S, N'@X xml', @X;
select datediff(second, @D, getdate());

set @S = 'declare @H int;
exec sp_xml_preparedocument @H output, @X;

select *
from openxml(@H, ''Table/row'', 0)
  with (' +
stuff((
      select top(@C) ', C'+cast(row_number() over(order by 1/0) as nvarchar(3))+ ' int'
      from sys.columns
      for xml path('')
      ), 1, 2, '') + ');
exec sys.sp_xml_removedocument @H';

set @D = getdate();
exec sp_executesql @S, N'@X xml', @X
select datediff(second, @D, getdate());

【讨论】:

  • from openxml 得一分。必须牢记在心。 Thx 和我的投票在这里以及链接的票。
  • "for ~~each~~ column" 好的,现在我的慢查询是有意义的。我得试着回到 OPENXML。 #LongLong2000 !感谢您提供这个关键线索。
【解决方案2】:

我真的很喜欢并投票给了 Mikael Eriksson 的回答,但它有一个方面:

他的测试生成了 909 KB 的 XML 文档,其中包含 1000 行、150 列。 而 sp_xml_preparedocument 在他的情况下只需要 226 毫秒(这真的很快),但是......

我尝试将它应用于我的 521 MB 的 XML 文档。 它包含 2045156 行,11 个不同的列,都被读取为 nvarchar(255)

当我通过 * 选择所有 11 列时:

  • 通过 .value() 选择 * 耗时 297 秒
  • 通过 openxml 选择 * 总共花费了 231 秒: (sp_xml_preparedocument 耗时 107 秒,从 openxml 中选择 * 耗时 123 秒)

openxml 在这种情况下效果更好!

当我只选择 2 列时:

  • 通过 .value() 选择 2 列耗时 57 秒
  • 通过 openxml 选择 2 列总共花费了 189 秒: (sp_xml_preparedocument - 86 秒,从 openxml 中选择 * - 103 秒)

.value() 在这种情况下效果更好!

所以看起来哪种方法更快实际上取决于您从 xml 查询的 xml 大小、行数和列数!

【讨论】:

    【解决方案3】:

    SQL-Server 处理 XML 的速度相当快,但你没有告诉我们最重要的事情:@x 来自哪里?

    在 SQL-Server 中,XML 不存储为您所看到的字符串,而是存储为 物理 表中的分层组织树。如果您在字符串基础上获取此 XML 并将其分配给类型为 XML 的变量,则引擎将不得不解析整个批次并将其所有内容传输到内部结构中。其余的应该相当快。

    乍一看有两个地方可以稍微调整一下:

    • FROM @x.nodes('//Table/row') T(c)
      // 将使用深度搜索,如果下面可能嵌套了另一个&lt;Table&gt;,引擎将查看每个&lt;row&gt;。而是使用FROM @x.nodes('/Table/row') T(c)

    • 并使用'nvarchar(50)' 而不是'varchar(50)'。 XML 在内部将其字符串存储为NVARCHAR。你可以避免所有这些演员...

    如果您拥有 SQL-Server 2016+ 并且您可以控制发件人,您可以试试JSON。这在 one-time-actions 中更好,因为它不会在内部结构中传输您的数据,然后才能使用它。

    【讨论】:

      【解决方案4】:

      您的选择取决于您对服务器的控制程度以及您愿意和能够做的准备工作。

      如果您能够在调用过程之前清理数据(例如,运行可执行文件)...

      您可以将数据反序列化为实体并使用您选择的 ORM 工具(nHibernate、EntityFramework 等)来存储实体。

      您可以将 XML 解析为批量导入器可以处理的对象,将其存储到文件中,并利用 sql 的批量导入功能。 https://docs.microsoft.com/en-us/sql/t-sql/statements/bulk-insert-transact-sql?view=sql-server-2017

      如果您能够在服务器上使用自定义功能,则可以使用 CLR 用户定义函数来完成这项工作,而不是在单独的可执行文件中运行它。 https://docs.microsoft.com/en-us/sql/relational-databases/clr-integration-database-objects-user-defined-functions/clr-user-defined-functions?view=sql-server-2017

      如果我想到其他任何内容,我会编辑这篇文章。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-10-17
        • 2011-08-10
        • 2019-10-09
        • 1970-01-01
        • 2018-02-01
        • 1970-01-01
        • 2013-07-29
        • 1970-01-01
        相关资源
        最近更新 更多