在 SQL 服务器中,使用 .modify() XQuery 删除节点需要 38 分钟才能执行

In SQL Server, deleting nodes using .modify() XQuery taking 38 minutes to execute

在SQL服务器中,我有一个带有XML类型临时变量的存储过程,我正在对该变量执行删除操作。当我在具有 4 个内核和 6 GB RAM 的本地 VM 中 运行 这个存储过程时,执行需要 24 秒。但是当我 运行 在具有 40 个内核和 128 GB RAM 的服务器中使用相同的存储过程时,此删除语句的执行时间超过 38 分钟。整个存储过程在此删除语句处被挂起 38 分钟。注释掉delete语句后,存储过程在服务器上执行8秒。我该如何解决这个性能问题。 SQL 服务器配置有问题吗?

DECLARE @PaymentData AS XML

SET @PaymentData = .....(Main XML Query)

SET @PaymentData.modify('delete //*[not(node())]')

@Mikael:以下是在服务器(具有 40 个内核和 128 GB RAM)上分解成行解决方案的执行计划 下面是我本地 VM 中的执行计划(具有 4 个内核和 6 GB RAM):

在我的机器上,删除花费了 1 小时 25 分钟,并给了我这个不太漂亮的查询计划。

该计划找到所有空节点(要删除的节点)并将它们存储在 Table 假脱机中。然后对于整个文档中的每个节点,检查该节点是否存在于假脱机中(嵌套循环(左半连接))以及是否从最终结果中排除该节点(合并连接(左反半连接) ). xml 然后从 UDX 运算符中的节点重建并分配给变量。 table spool 没有索引,因此对于每个需要检查的节点,将扫描整个 spool(或直到找到匹配项)。

这实质上意味着该算法的性能是 O(n*d),其中 n 是节点总数,d 是删除节点总数。

有几个可能的解决方法。

首先,也许最好的办法是,如果您可以修改 XML 查询,使其首先不生成空节点。如果您使用 for xml 创建 XML 则完全可能,如果您已经将 XML 的一部分存储在 table.

中则可能不可能

另一种选择是在 Row 上切碎 XML(参见下面的示例 XML),将结果放入 table 变量,修改 XML 在 table 变量中,然后重新创建组合的 XML.

declare @T table(PaymentData xml);

insert into @T 
select T.X.query('.')
from @PaymentData.nodes('Row') as T(X);

update @T
set PaymentData.modify('delete //*[not(node())]');

select T.PaymentData as '*'
from @T as T
for xml path('');

这将为您提供 O(n*s*d) 的性能特征,其中 nrow 节点的数量,s 是每个 [= 的子节点数量21=] 节点和 d 是每个 row 节点删除的行数。

我真的不能推荐的第三个选项是使用一个未记录的跟踪标志,它在计划中删除了假脱机的使用。您可以在测试中试用它,或者您可以捕获生成的计划并在计划指南中使用它。

declare @T table(PaymentData xml);

insert into @T values(@PaymentData);

update @T 
set PaymentData.modify('delete //*[not(node())]')
option (querytraceon 8690);

select @PaymentData = PaymentData
from @T;

带有跟踪标志的查询计划:

这个版本在我的电脑上用了 4 秒,而不是 1 小时 25 分钟。

将 XML 分解为 table 变量的多行总共花费了 6 秒来执行。

完全不必删除任何行当然是最快的。

示例数据,12000 个节点和 32 个子节点,如果您想在家尝试,其中 2 个为空。

declare @PaymentData as xml;

set @PaymentData = (
                   select top(12000) 
                     1 as N1, 1 as N2, 1 as N3, 1 as N4, 1 as N5, 1 as N6, 1 as N7, 1 as N8, 1 as N9, 1 as N10, 
                     1 as N11, 1 as N12, 1 as N13, 1 as N14, 1 as N15, 1 as N16, 1 as N17, 1 as N18, 1 as N19, 1 as N20, 
                     1 as N21, 1 as N22, 1 as N23, 1 as N24, 1 as N25, 1 as N26, 1 as N27, 1 as N28, 1 as N29, 1 as N30,
                     '' as N31,
                     '' as N32
                   from sys.columns as c1, sys.columns as c2
                   for xml path('Row')
                   );

注意:我不知道为什么在你们的一台服务器上执行只需要 24 秒。我建议您重新检查 XML 实际上是否相同。或者为什么不使用我为您提供的 XML 示例进行测试。

更新:

对于粉碎版本,删除查询中假脱机的问题可以转移到粉碎查询,而不是让你有同样糟糕的性能。然而,这并不总是正确的。我看过没有线轴的计划和有线轴的计划,但我不知道为什么有时有线轴,为什么有时没有。

我还发现,如果您使用临时 table 而不是 insert ... into,我不会在粉碎查询中得到假脱机。

select T.X.query('.') as PaymentData
into #T
from @PaymentData.nodes('Row') as T(X);

update #T
set PaymentData.modify('delete //*[not(node())]');