在 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)
的性能特征,其中 n
是 row
节点的数量,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())]');
在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)上分解成行解决方案的执行计划
在我的机器上,删除花费了 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)
的性能特征,其中 n
是 row
节点的数量,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())]');