【问题标题】:cross apply xml query performs exponentially worse as xml document grows随着 xml 文档的增长,交叉应用 xml 查询的性能呈指数级下降
【发布时间】:2014-08-03 11:50:39
【问题描述】:

我有什么

我有一个可变大小的 XML 文档,需要在 MSSQL 2008 R2 上进行解析,如下所示:

<data item_id_type="1" cfgid="{4F5BBD5E-72ED-4201-B741-F6C8CC89D8EB}" has_data_event="False">
  <item name="1">
    <field id="{EA032B25-19F1-4C1B-BDDE-3113542D13A5}" type="2">0.506543009706267</field>
    <field id="{71014ACB-571B-4C72-9C9B-05458B11335F}" type="2">-0.79500402346138</field>
    <field id="{740C36E9-1988-413E-A1D5-B3E5B4405B45}" type="2">0.0152649050024924</field>
  </item>
  <item name="2">
    <field id="{EA032B25-19F1-4C1B-BDDE-3113542D13A5}" type="2">0.366096802804087</field>
    <field id="{71014ACB-571B-4C72-9C9B-05458B11335F}" type="2">-0.386642801354842</field>
    <field id="{740C36E9-1988-413E-A1D5-B3E5B4405B45}" type="2">0.031671174184115</field>
  </item>
</data>

.

我想要什么

我需要将其转换为如下所示的常规表格类型数据集:

item_name field_id                             field_type  field_value
--------- ------------------------------------ ----------- ---------------
1         EA032B25-19F1-4C1B-BDDE-3113542D13A5 2           0.5065430097062
1         71014ACB-571B-4C72-9C9B-05458B11335F 2           -0.795004023461
1         740C36E9-1988-413E-A1D5-B3E5B4405B45 2           0.0152649050024
2         EA032B25-19F1-4C1B-BDDE-3113542D13A5 2           0.3660968028040
2         71014ACB-571B-4C72-9C9B-05458B11335F 2           -0.386642801354
2         740C36E9-1988-413E-A1D5-B3E5B4405B45 2           0.0316711741841
3         EA032B25-19F1-4C1B-BDDE-3113542D13A5 2           0.8839620369590
3         71014ACB-571B-4C72-9C9B-05458B11335F 2           -0.781459993268
3         740C36E9-1988-413E-A1D5-B3E5B4405B45 2           0.2284423515729

.

什么有效

cross apply 查询创建所需的输出:

create table #temp (x xml)

insert into #temp (x)
values ('
<data item_id_type="1" cfgid="{4F5BBD5E-72ED-4201-B741-F6C8CC89D8EB}" has_data_event="False">
  <item name="1">
    <field id="{EA032B25-19F1-4C1B-BDDE-3113542D13A5}" type="2">0.506543009706267</field>
    <field id="{71014ACB-571B-4C72-9C9B-05458B11335F}" type="2">-0.79500402346138</field>
    <field id="{740C36E9-1988-413E-A1D5-B3E5B4405B45}" type="2">0.0152649050024924</field>
  </item>
  <item name="2">
    <field id="{EA032B25-19F1-4C1B-BDDE-3113542D13A5}" type="2">0.366096802804087</field>
    <field id="{71014ACB-571B-4C72-9C9B-05458B11335F}" type="2">-0.386642801354842</field>
    <field id="{740C36E9-1988-413E-A1D5-B3E5B4405B45}" type="2">0.031671174184115</field>
  </item>
  <item name="3">
    <field id="{EA032B25-19F1-4C1B-BDDE-3113542D13A5}" type="2">0.883962036959074</field>
    <field id="{71014ACB-571B-4C72-9C9B-05458B11335F}" type="2">-0.781459993268713</field>
    <field id="{740C36E9-1988-413E-A1D5-B3E5B4405B45}" type="2">0.228442351572923</field>
  </item>
</data>
')

select c.value('(../@name)','varchar(5)') as item_name
      ,c.value('(@id)','uniqueidentifier') as field_id
      ,c.value('(@type)','int') as field_type
      ,c.value('(.)','nvarchar(15)') as field_value
from   #temp cross apply
       #temp.x.nodes('/data/item/field') as y(c)

drop table #temp

.

问题

当 XML 中有几百个(或更少)&lt;item&gt; 元素时,查询执行得很好。但是,当有 1,000 个 &lt;item&gt; 元素时,在 SSMS 中完成返回行需要 24 秒。当有 6,500 个 &lt;item&gt; 元素时,运行 cross apply 查询大约需要 20 分钟。我们可以有 10-20,000 个&lt;item&gt; 元素。

.

问题

是什么让cross apply 查询在这个简单的 XML 文档上表现如此糟糕,并且随着数据集的增长呈指数级下降?

有没有更有效的方法将 XML 文档转换为表格数据集(在 SQL 中)?

【问题讨论】:

    标签: sql-server performance sql-server-2008-r2 sqlxml cross-apply


    【解决方案1】:

    是什么让交叉应用查询在这个简单的 XML 上表现如此糟糕 文档,并随着数据集的增长而呈指数级变慢?

    是利用父轴从item节点获取属性ID。

    正是这部分查询计划有问题。

    注意下面的表值函数有 423 行。

    再添加一个带有三个字段节点的项目节点就可以了。

    返回 732 行。

    如果我们将第一个查询的节点加倍到总共 6 个项目节点会怎样?

    我们最多返回 1602 行。

    顶部函数中的图 18 是 XML 中的所有字段节点。我们这里有 6 个项目,每个项目中有 3 个字段。这 18 个节点用于嵌套循环连接另一个函数,因此 18 次执行返回 1602 行,每次迭代返回 89 行。这恰好是整个 XML 中节点的确切数量。好吧,它实际上比所有可见节点多一个。我不知道为什么。您可以使用此查询来检查 XML 中的节点总数。

    select count(*)
    from @XML.nodes('//*, //@*, //*/text()') as T(X)  
    

    因此,当您在 values 函数中使用父轴 .. 时,SQL Server 用于获取值的算法是它首先找到您要粉碎的所有节点,最后一种情况是 18 个。对于这些节点中的每一个,它都会分解并返回整个 XML 文档,并在过滤器运算符中检查您实际需要的节点。在那里你有你的指数增长。 您应该使用一个额外的交叉应用,而不是使用父轴。先在物品上切碎,然后在场地上切碎。

    select I.X.value('@name', 'varchar(5)') as item_name,
           F.X.value('@id', 'uniqueidentifier') as field_id,
           F.X.value('@type', 'int') as field_type,
           F.X.value('text()[1]', 'nvarchar(15)') as field_value
    from #temp as T
      cross apply T.x.nodes('/data/item') as I(X)
      cross apply I.X.nodes('field') as F(X)
    

    我还更改了访问字段文本值的方式。使用. 将使SQL Server 去寻找field 的子节点并将这些值连接到结果中。您没有子值,因此结果是相同的,但最好避免在查询计划中包含该部分(UDX 运算符)。

    如果您使用的是 XML 索引,则查询计划不存在父轴的问题,但您仍然可以从更改获取字段值的方式中受益。

    【讨论】:

    • 希望我能 +5。感谢您的详细解释。 XML 查询现在更有意义了。
    • 干得好!我正在使用 OPENROWSET 和 .query() 方法,但性能结果很糟糕。您的解决方案将整个处理时间缩短到不到一分钟!
    • +1 建议逐个节点分解(通过cross apply),使用text(),并包括执行计划结果。
    【解决方案2】:

    添加一个 XML 索引就可以了。现在,运行 20 分钟的 6,500 条记录只用了不到 4 秒。

    create table #temp (id int primary key, x xml)
    create primary xml index idx_x on #temp (x)
    
    insert into #temp (id, x)
    values (1, '
    <data item_id_type="1" cfgid="{4F5BBD5E-72ED-4201-B741-F6C8CC89D8EB}" has_data_event="False">
      <item name="1">
        <field id="{EA032B25-19F1-4C1B-BDDE-3113542D13A5}" type="2">0.506543009706267</field>
        <field id="{71014ACB-571B-4C72-9C9B-05458B11335F}" type="2">-0.79500402346138</field>
        <field id="{740C36E9-1988-413E-A1D5-B3E5B4405B45}" type="2">0.0152649050024924</field>
      </item>
      <item name="2">
        <field id="{EA032B25-19F1-4C1B-BDDE-3113542D13A5}" type="2">0.366096802804087</field>
        <field id="{71014ACB-571B-4C72-9C9B-05458B11335F}" type="2">-0.386642801354842</field>
        <field id="{740C36E9-1988-413E-A1D5-B3E5B4405B45}" type="2">0.031671174184115</field>
      </item>
      <item name="3">
        <field id="{EA032B25-19F1-4C1B-BDDE-3113542D13A5}" type="2">0.883962036959074</field>
        <field id="{71014ACB-571B-4C72-9C9B-05458B11335F}" type="2">-0.781459993268713</field>
        <field id="{740C36E9-1988-413E-A1D5-B3E5B4405B45}" type="2">0.228442351572923</field>
      </item>
    </data>
    ')
    
    select c.value('(../@name)','varchar(5)') as item_name
          ,c.value('(@id)','uniqueidentifier') as field_id
          ,c.value('(@type)','int') as field_type
          ,c.value('(.)','nvarchar(15)') as field_value
    from   #temp cross apply
           #temp.x.nodes('/data/item/field') as y(c)
    
    drop table #temp
    

    【讨论】:

    • James,你可能想检查一下你的基础知识——tempdb 中的表名和索引名(即临时)都带有前缀,因此 1000 个用户可以拥有具有相同索引的同一张表,并且不会发生冲突。至少对于非 xml 索引是这样;)
    • @TomTom - 你是对的。我记得在 90 年代就遇到过这个问题。不记得是哪个 DBMS。很高兴知道这不再是问题了!
    • @TomTom 您的评论过于简单,因为它仅适用于单哈希前缀 #tempTables。如果临时表名为##somethingLikeThis,即以两个哈希为前缀,则它肯定全局的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-08-10
    • 2018-06-08
    • 1970-01-01
    • 1970-01-01
    • 2022-10-16
    • 2023-02-23
    • 2019-01-12
    相关资源
    最近更新 更多