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

Rak*_*esh 6 xml sql-server xquery

在SQL Server中,我有一个带有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())]')
Run Code Online (Sandbox Code Playgroud)

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

Mik*_*son 7

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

在此输入图像描述

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

基本上意味着该算法的性能是O(n*d)其中n是节点的总数量,d是总数或删除节点.

有几种可能的解决方法.

首先,也许最好的是,如果您可以修改XML查询,以便首先不生成空节点.for xml如果您已经创建了XML,并且如果您已经将XML的一部分存储在表中,则可能无法创建XML .

另一种选择是将XML分解Row(参见下面的示例XML),将结果放在表变量中,修改表变量中的XML,然后重新创建组合的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('');
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

这会给你的性能特性O(n*s*d),其中n是多少row节点,s为每个子节点的数量row的节点和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;
Run Code Online (Sandbox Code Playgroud)

带跟踪标志的查询计划:

在此输入图像描述

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

将XML分解为多个行到表变量总共需要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')
                   );
Run Code Online (Sandbox Code Playgroud)

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

更新:

对于碎化版本,删除查询中的假脱机问题可以移动到碎化查询,而不会给您带来大致相同的不良性能.然而,这并非总是如此.我已经看到没有线轴的计划和有线轴的计划,我不知道为什么它有时存在,为什么它不在其他时候.

我还发现如果你使用临时表而不是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())]');
Run Code Online (Sandbox Code Playgroud)