Ril*_*jor 7 xml sql-server optimization functions sql-server-2016
我们有内联函数,可以将数据收集到 XML 中,将派生的 XML 传递给其他函数,然后将其切碎并将其重组为字符串。
(你的“你不应该在 T-SQL 中做那种事情”是另一天的讨论。)
多年来,这在 2005 和 2008 R2 中一直运行良好。我们现在正在升级到 2016 SP1。使用这些函数在我们的生产服务器上运行不到一秒的查询现在在 2016 SP1 中运行得更快。那太好了,但是在 2016 SP1 上编译需要一个小时。严重地:
生产环境:
(它们都来自 SQL Sentry Plan Explorer。)
我们已经在 2008 (100) 和 2016 (130) 兼容性级别尝试了数据库(但我们还没有使用“传统基数估计”和“查询优化器修复”设置,目前这两个设置都为“关闭”)。我们尝试过使用QUERYTRACEON 9481
,似乎没有效果。
同样,这与最终的计划无关,因为它们都在很短的时间内运行。这是制定计划所需的时间。
我们已经设法用一组简化的代码在一定程度上重现了这个问题。调用以下示例中的顶级函数的语句在 SQL Server 2016 (SP1-CU5) 上编译需要 30-60 秒,但在 SQL Server 2008 R2 (SP3) 上编译和运行只需不到 1 秒。
/*
Create and populate table...
*/
CREATE TABLE TestXMLStuff (OrderID int, ProdLength int, ProdWidth int, ProdHeight int);
INSERT INTO TestXMLStuff (OrderID, ProdLength, ProdWidth, ProdHeight) VALUES
(1, 10, 15, 20),
(1, 15, 20, 25),
(2, 20, 25, 30),
(2, 25, 30, 35),
(2, 30, 35, 40);
GO
/*
Function which accepts XML, shreds it and reforms it as a string...
*/
CREATE FUNCTION TestCalc
(
@T varchar(8000),
@X xml
)
RETURNS TABLE
AS
RETURN
WITH p AS
(
SELECT
LF = CHAR(13) + CHAR(10),
Tab = CHAR(9),
T = isNull(@T,'')
), pid AS
(
SELECT
isNull(ProdInfoXMLTable.ProdInfoXML.query('(/ProdInfo)').value('(.)[1]','varchar(max)'),'') AS ProdInfoText
FROM (
SELECT
ProdInfoXML =
(
SELECT
ProdInfo =
CASE WHEN Products.ProdNum > 1 THEN '--' + p.LF ELSE '' END +
'Product Number: ' + CONVERT(varchar(50),Products.ProdNum) + p.LF +
CASE WHEN Products.ProdLength = '' THEN '' ELSE p.Tab + 'Length: ' + Products.ProdLength + p.LF END +
CASE WHEN Products.ProdWidth = '' THEN '' ELSE p.Tab + 'Width: ' + Products.ProdHeight + p.LF END +
CASE WHEN Products.ProdHeight = '' THEN '' ELSE p.Tab + 'Height: ' + Products.ProdHeight + p.LF END
FROM (
SELECT
ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS ProdNum,
isNull(P.X.value('(./Length)[1]','varchar(500)'),'') AS ProdLength,
isNull(P.X.value('(./Width)[1]','varchar(500)'),'') AS ProdWidth,
isNull(P.X.value('(./Height)[1]','varchar(500)'),'') AS ProdHeight
FROM @x.nodes('/Products/Product') AS P(X)
) AS Products
CROSS JOIN p
FOR XML PATH(''), TYPE
)
) AS ProdInfoXMLTable
)
SELECT
Final = p.T + p.LF + p.LF + pid.ProdInfoText
FROM p
CROSS JOIN pid;
GO
/*
Function to create XML in the format required for TestCalc...
*/
CREATE FUNCTION TestGetXML
(
@N int
)
RETURNS TABLE
AS
RETURN
WITH p AS
(
SELECT
N = isNull(@N,0)
)
SELECT
ProdInfoXML =
(
SELECT
[Length] = ProdData.ProdLength,
[Width] = ProdData.ProdWidth,
[Height] = ProdData.ProdHeight
FROM TestXMLStuff ProdData
WHERE ProdData.OrderID = @N
FOR XML PATH('Product'), ROOT('Products'), TYPE
);
GO
/*
Function to join the other two functions, gathering the XML and feeding it to the string creator which shreds and reforms it...
*/
CREATE FUNCTION TestGetFromTableUsingFunc
(
@N int
)
RETURNS TABLE
AS
RETURN
WITH p AS
(
SELECT
N = isNull(@N,0)
)
SELECT
FinalResult = 'This is a ' + TestCalcResults.Final
FROM p
CROSS APPLY TestGetXML
(
p.N
) AS x
CROSS APPLY TestCalc
(
'test',
x.ProdInfoXML
) AS TestCalcResults;
GO
/*
Code to call the function. This is what takes around 60 seconds to compile on our 2016 system but basically no time on the 2008 R2 system.
*/
SELECT *
FROM TestXMLStuff
CROSS APPLY TestGetFromTableUsingFunc
(
OrderID
)
OPTION (RECOMPILE);
GO
Run Code Online (Sandbox Code Playgroud)
生产@@version
其中编译是不是一个问题:
Microsoft SQL Server 2008 R2 (SP3) - 10.50.6000.34 (X64)
Run Code Online (Sandbox Code Playgroud)
测试@@version
编译需要“永远”的地方:
Microsoft SQL Server 2016 (SP1-CU5) (KB4040714) - 13.0.4451.0 (X64)
Run Code Online (Sandbox Code Playgroud)
为什么从 2008 R2 到 2016 的编译时间会如此缩短?
这个答案是否揭示了解决这一困境的简单方法,而无需改变所有这些工作方式?(我希望有神奇的跟踪标志或即将到来的 Microsoft 更新。)
(如果这是 SQL Server 2017,我将使用 JSON 来收集和传递数据,这似乎更快且开销更低,然后我将使用 JSON 函数来切碎并STRING_AGG
重新转换为文本。但唉,现在还没有可用的。)
根据Joe Obbish的提示,我使用以下代码收集了使用跟踪标志 8675时的结果:
DBCC TRACEON(3604)
SELECT *
FROM TestXMLStuff
CROSS APPLY TestGetFromTableUsingFunc
(
OrderID
)
OPTION (RECOMPILE, QUERYTRACEON 8675);
DBCC TRACEOFF(3604)
Run Code Online (Sandbox Code Playgroud)
在 2008 R2 实例上,它只用了不到一秒钟的时间,并生成了以下内容:
/*
Create and populate table...
*/
CREATE TABLE TestXMLStuff (OrderID int, ProdLength int, ProdWidth int, ProdHeight int);
INSERT INTO TestXMLStuff (OrderID, ProdLength, ProdWidth, ProdHeight) VALUES
(1, 10, 15, 20),
(1, 15, 20, 25),
(2, 20, 25, 30),
(2, 25, 30, 35),
(2, 30, 35, 40);
GO
/*
Function which accepts XML, shreds it and reforms it as a string...
*/
CREATE FUNCTION TestCalc
(
@T varchar(8000),
@X xml
)
RETURNS TABLE
AS
RETURN
WITH p AS
(
SELECT
LF = CHAR(13) + CHAR(10),
Tab = CHAR(9),
T = isNull(@T,'')
), pid AS
(
SELECT
isNull(ProdInfoXMLTable.ProdInfoXML.query('(/ProdInfo)').value('(.)[1]','varchar(max)'),'') AS ProdInfoText
FROM (
SELECT
ProdInfoXML =
(
SELECT
ProdInfo =
CASE WHEN Products.ProdNum > 1 THEN '--' + p.LF ELSE '' END +
'Product Number: ' + CONVERT(varchar(50),Products.ProdNum) + p.LF +
CASE WHEN Products.ProdLength = '' THEN '' ELSE p.Tab + 'Length: ' + Products.ProdLength + p.LF END +
CASE WHEN Products.ProdWidth = '' THEN '' ELSE p.Tab + 'Width: ' + Products.ProdHeight + p.LF END +
CASE WHEN Products.ProdHeight = '' THEN '' ELSE p.Tab + 'Height: ' + Products.ProdHeight + p.LF END
FROM (
SELECT
ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS ProdNum,
isNull(P.X.value('(./Length)[1]','varchar(500)'),'') AS ProdLength,
isNull(P.X.value('(./Width)[1]','varchar(500)'),'') AS ProdWidth,
isNull(P.X.value('(./Height)[1]','varchar(500)'),'') AS ProdHeight
FROM @x.nodes('/Products/Product') AS P(X)
) AS Products
CROSS JOIN p
FOR XML PATH(''), TYPE
)
) AS ProdInfoXMLTable
)
SELECT
Final = p.T + p.LF + p.LF + pid.ProdInfoText
FROM p
CROSS JOIN pid;
GO
/*
Function to create XML in the format required for TestCalc...
*/
CREATE FUNCTION TestGetXML
(
@N int
)
RETURNS TABLE
AS
RETURN
WITH p AS
(
SELECT
N = isNull(@N,0)
)
SELECT
ProdInfoXML =
(
SELECT
[Length] = ProdData.ProdLength,
[Width] = ProdData.ProdWidth,
[Height] = ProdData.ProdHeight
FROM TestXMLStuff ProdData
WHERE ProdData.OrderID = @N
FOR XML PATH('Product'), ROOT('Products'), TYPE
);
GO
/*
Function to join the other two functions, gathering the XML and feeding it to the string creator which shreds and reforms it...
*/
CREATE FUNCTION TestGetFromTableUsingFunc
(
@N int
)
RETURNS TABLE
AS
RETURN
WITH p AS
(
SELECT
N = isNull(@N,0)
)
SELECT
FinalResult = 'This is a ' + TestCalcResults.Final
FROM p
CROSS APPLY TestGetXML
(
p.N
) AS x
CROSS APPLY TestCalc
(
'test',
x.ProdInfoXML
) AS TestCalcResults;
GO
/*
Code to call the function. This is what takes around 60 seconds to compile on our 2016 system but basically no time on the 2008 R2 system.
*/
SELECT *
FROM TestXMLStuff
CROSS APPLY TestGetFromTableUsingFunc
(
OrderID
)
OPTION (RECOMPILE);
GO
Run Code Online (Sandbox Code Playgroud)
在 2016 SP1-CU5 实例上,花费了 1 分 11 秒并生成了以下内容:
Microsoft SQL Server 2008 R2 (SP3) - 10.50.6000.34 (X64)
Run Code Online (Sandbox Code Playgroud)
虽然还有更多的事情发生,但看起来过去的时间只有 0.018(秒?),比 2008 R2 上的 0.03 少。所以这个编译/优化/执行阶段一定不会花这么长时间。但肯定是有的。
我们最终的 2016 生产实例将具有与当前生产 2008 R2 相同的“硬件”。Test/Dev 2016 实例具有不同的规格,因此这些比较不是苹果对苹果的比较。产品是 78 演出。开发是 16 演出。但是我在另一个 2008 R2 盒子上测试了 20 gig,速度很快。此外,我们正在谈论索引良好的少量数据。并且在编译期间很少 IO 但有很多 CPU。
我可以看到统计数据是命中真实(大)表的真实函数的一个问题,但在我人为的简化示例中,对5个满载整数的行进行一些简单的 XML/文本操作需要 1 分钟以上的时间。我可以更快地输入结果。:) 我相信我们对生产有自动统计,而且我没有看到其他看似与统计相关的性能问题。另一个非生产 2008 R2 开发/测试环境(具有与 2016 开发/测试类似的陈旧生产副本)是 lickety-split。
SQL Server 2016 SP1 的 CU7可能会解决您的问题。相关知识库文章似乎是KB 4056955 - FIX:将字符串或二进制数据转换为 XML 的查询需要很长时间才能在 SQL Server 2016 中编译。
我在 SQL Server 2016 SP1 CU6 中运行了您的重现代码,并获得了以下编译时间SET STATISTICS TIME ON
:
SQL Server 解析和编译时间:CPU 时间 = 24437 毫秒,经过时间 = 24600 毫秒。
SQL Server 解析和编译时间:CPU 时间 = 48968 毫秒,已用时间 = 49451 毫秒。
以下是升级到 SP1 CU7 后编译时间的变化:
SQL Server 解析和编译时间:CPU 时间 = 16 毫秒,经过时间 = 16 毫秒。
SQL Server 解析和编译时间:CPU 时间 = 16 毫秒,经过时间 = 17 毫秒。
SQL Server 解析和编译时间:CPU 时间 = 16 毫秒,经过时间 = 17 毫秒。
SQL Server 解析和编译时间:CPU 时间 = 44 毫秒,经过时间 = 44 毫秒。
它大约快 750 倍。
归档时间: |
|
查看次数: |
938 次 |
最近记录: |