【问题标题】:Why is VALUES(CONVERT(XML,'...')) much slower than VALUES(@xml)?为什么 VALUES(CONVERT(XML,'...')) 比 VALUES(@xml) 慢得多?
【发布时间】:2015-08-18 21:04:20
【问题描述】:

我想创建一个子查询,它生成一个数字列表作为单列结果,类似于 MindLoggedOut did here 但没有 @x xml 变量,以便它可以附加到 WHERE 表达式为没有 sql 参数的纯字符串(子查询)。问题是参数(或变量)的替换使查询运行慢了 5000 倍,我不明白为什么。是什么导致了如此大的差异?

例子:

/* Create a minimalistic xml like <b><a>78</a><a>91</a>...</b> */
DECLARE @p_str VARCHAR(MAX) =
    '78 91 01 12 34 56 78 91 01 12 34 56 78 91 01 12 34 56';
DECLARE @p_xml XML = CONVERT(XML,
  '<b><a>'+REPLACE(@p_str,' ','</a><a>')+'</a></b>'
);

SELECT a.value('(child::text())[1]','INT')
FROM (VALUES (@p_xml)) AS t(x)
CROSS APPLY x.nodes('//a') AS x(a);

这每行返回一个数字并且速度非常快(比我目前使用的字符串拆分器方法快 20 倍,similartothese。 我在 sql server CPU 时间方面测量了 20 倍的加速,@p_str 包含 3000 个数字。)

现在如果我将@p_xml 的定义内联到查询中:

SELECT a.value('(child::text())[1]','INT')
FROM (VALUES (CONVERT(XML,
  '<b><a>'+REPLACE(@p_str,' ','</a><a>')+'</a></b>'
))) AS t(x)
CROSS APPLY x.nodes('//a') AS x(a);

然后它变得慢了 5000 倍(当 @p_str 包含数千个数字时。)查看查询计划我找不到原因。

first query…VALUES(@p_xml)…)和the second…VALUES(CONVERT(XML,'...'))…)的计划

有人能解释一下吗?

更新

显然第一个查询的计划不包括成本 @p_xml = CONVERT(XML, ...REPLACE(...)... ) 的分配,但是这个 成本不是可以解释 46 毫秒与 234 秒的罪魁祸首 整个脚本的执行时间之间的差异(当 @p_str 很大)。这种差异是系统性的(不是随机的) 并且实际上是在 SqlAzure(S1 层)中观察到的。

此外,当我重写查询时:将CONVERT(XML,...) 替换为用户定义的标量函数:

SELECT a.value('(child::text())[1]','INT')
FROM (VALUES (dbo.MyConvertToXmlFunc(
  '<b><a>'+REPLACE(@p_str,' ','</a><a>')+'</a></b>'
))) AS t(x)
CROSS APPLY x.nodes('//a') AS x(a);

dbo.MyConvertToXmlFunc() 在哪里:

CREATE FUNCTION dbo.MyConvertToXmlFunc(@p_str NVARCHAR(MAX))
RETURNS XML BEGIN
  RETURN CONVERT(XML, @p_str);
END;

差异消失了 (plan)。所以至少我有一个解决方法......但想了解它。

【问题讨论】:

  • 您能否发布 2 个查询之间的 sql 执行计划的图片,以便我们查看差异?
  • 我怀疑这是因为第一个执行计划测量的少;即 @p_xml 的分配(因此从字符串到 xml 的相关对话)不会出现在执行计划中,所以你不会像这样比较。
  • 实际计划有什么不同吗?估计的行数有什么不同吗?

标签: sql-server xml performance sql-execution-plan


【解决方案1】:

这与this answer by Paul White中描述的问题基本相同。

我尝试了一个长度为 10,745 个字符的字符串,其中包含 3,582 个项目。

使用字符串文字的执行计划最终执行字符串替换并将整个字符串转换为 XML 两次(总共 7,164 次)。

有问题的sqltses.dll!CEsExec::GeneralEval4 调用在下面的跟踪中突出显示。整个调用堆栈的 CPU 时间为 22.38%(几乎是四核上的单核)。 - 这两个电话占了其中的 92%。

在每次通话中,sqltses.dll!ConvertFromStringTypesAndXmlToXmlsqltses.dll!BhReplaceBhStrStr 都花费几乎相同的时间。

我在下面的计划中使用了相同的颜色编码。

执行计划的底部分支对字符串中的每个拆分项执行一次。

右下角有问题的表值函数在其open 方法中。该函数的参数列表是

标量运算符([Expr1000]),

标量运算符((7)),

标量运算符(带有 XPath 过滤器的 XML 阅读器。[id]),

标量运算符(getdescendantlimit(XML Reader with XPath filter.[id]))

对于 Stream Aggregate,问题出在其 getrow 方法中。

[Expr1010] = Scalar Operator(MIN(
SELECT CASE
         WHEN [Expr1000] IS NULL
           THEN NULL
         ELSE
           CASE
             WHEN datalength([XML Reader with XPath filter].[value]) >= ( 128 )
               THEN CONVERT_IMPLICIT(int, [XML Reader with XPath filter].[lvalue], 0)
             ELSE CONVERT_IMPLICIT(int, [XML Reader with XPath filter].[value], 0)
           END
       END 
))

这两个表达式都引用Expr1000(尽管流聚合只是为了检查它是否是NULL

这在右上方的恒定扫描中定义如下。

(Scalar Operator(CONVERT(xml,'<b><a>'+replace([@p_str],' '
,CONVERT_IMPLICIT(varchar(max),'</a><a>',0))+'</a></b>',0)))

从跟踪中可以清楚地看出,该问题与先前链接的答案相同,并且在缓慢的计划中反复重新评估。当作为参数传递时,昂贵的计算只会发生一次。


编辑:我刚刚意识到这实际上与 Paul White blogged about here 的计划和问题几乎完全相同 - 我的测试与所描述的测试相比,唯一的区别是我发现字符串 Replace 和 XML 转换是在VARCHAR(MAX) 情况下彼此一样糟糕 - 并且字符串替换在非最大情况下超过转换成本。

最大

非最大值

(2000 个字符的源字符串,包含 668 个项目。替换后的 6010 个字符)

在这个测试中,替换几乎是 xml 转换的 CPU 成本的两倍。它似乎是通过使用来自熟悉的 TSQL 函数 CHARINDEXSTUFF 的代码来实现的,其中将字符串转换为 unicode 会占用大量时间。我认为我的结果与 Paul 报告的结果之间的这种差异归结为整理(从 Latin1_General_CS_AS 切换到 SQL_Latin1_General_CP1_CS_AS 可以显着降低字符串替换的成本)

【讨论】:

  • Paul White 的blog article 不仅讨论了我试图实现的相同示例/用例,而且他提出了比我发现的更好的解决方法。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-26
  • 2011-01-25
  • 2012-12-20
  • 1970-01-01
相关资源
最近更新 更多