我想创建一个子查询,它将一个数字列表生成为单列结果,类似于MindLoggedOut did here但没有@x
xml变量,因此它可以附加到{ {1}}表达式为没有sql参数的纯字符串(子查询)。问题是更换参数(或变量)会使查询运行慢5000倍,我不明白为什么。是什么导致这种巨大差异?
示例:
WHERE
这会返回每行一个数字并且非常快(比我目前使用的字符串分割器方法快20倍,similar to these。
我测量了sql server CPU时间的20倍加速,/* 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);
包含3000个数字。)
现在,如果我将@p_str
的定义内联到查询中:
@p_xml
然后它慢了5000倍(当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);
包含数千个数字时。)查看查询计划我找不到它的原因。
计划first query(@p_str
)和the second(…VALUES(@p_xml)…
)
有人可以对此有所了解吗?
更新
显然,第一个查询的计划不包括费用
…VALUES(CONVERT(XML,'...'))…
任务,但是这个
费用不是可以解释46毫秒与234秒的罪魁祸首
整个脚本的执行时间之间的差异(何时
@p_xml = CONVERT(XML, ...REPLACE(...)... )
很大)。这种差异是系统的(不是随机的)
实际上是在SqlAzure(S1层)中观察到的。
此外,当我重写查询时:用用户定义的标量函数替换@p_str
:
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()
差异消失了(plan)。所以至少我有一个解决方法......但是想了解它。
答案 0 :(得分:5)
这基本上与this answer by Paul White中描述的问题相同。
我尝试了一个长度为10,745个字符的字符串,其中包含3,582个项目。
带有字符串文字的执行计划最终执行字符串替换并将每个项目的整个字符串转换为XML两次(总共7,164次)。
以下跟踪中突出显示有问题的sqltses.dll!CEsExec::GeneralEval4
来电。整个调用堆栈的CPU时间为22.38%(几乎最大化四核上的单核)。 - 其中92%用于这两次通话。
在每次通话中sqltses.dll!ConvertFromStringTypesAndXmlToXml
和sqltses.dll!BhReplaceBhStrStr
都需要几乎相同的时间。
我在下面的计划中使用了相同的颜色编码。
执行计划的底部分支对字符串中的每个拆分项执行一次。
右下角有问题的表值函数是open
方法。该函数的参数列表是
标量运算符([Expr1000]),
标量算子((7)),
标量运算符(带有XPath过滤器的XML阅读器。[id]),
标量运算符(getdescendantlimit(带XPath过滤器的XML阅读器。[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函数CHARINDEX
和STUFF
的代码来实现的,并且需要花费大量时间将字符串转换为unicode。我认为我的结果与Paul报告的结果之间存在的这种差异归结为整理(从Latin1_General_CS_AS切换到SQL_Latin1_General_CP1_CS_AS会显着降低字符串替换的成本)