Jam*_*olt 13 sql-server optimization substring sql-server-2016 cardinality-estimates
是否有关于 SQL Server 2016 中关于如何估计包含 SUBSTRING() 或其他字符串函数的谓词的基数的更改的任何文档或研究?
我问的原因是我正在查看一个在兼容模式 130 下性能下降的查询,原因与与包含对 SUBSTRING() 调用的 WHERE 子句匹配的行数估计值的变化有关。我通过查询重写更正了这个问题,但我想知道是否有人知道有关 SQL Server 2016 中该领域更改的任何文档。
演示代码如下。在这个测试案例中,估计值非常接近,但准确度因数据而异。
在测试用例中,在兼容级别 120 中,SQL Server 似乎使用直方图进行估计,而在兼容级别 130 中,SQL Server 似乎假定表的固定 10% 匹配。
CREATE DATABASE MyStringTestDB;
GO
USE MyStringTestDB;
GO
DROP TABLE IF EXISTS dbo.StringTest;
CREATE TABLE dbo.StringTest ( [TheString] varchar(15) );
GO
INSERT INTO dbo.StringTest
VALUES
( 'Y5_CLV' );
INSERT INTO dbo.StringTest
VALUES
( 'Y5_EG3' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_NE' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_PQT' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_T2V' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_TT4' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_ZKK' );
INSERT INTO dbo.StringTest
VALUES
( 'ZZ_LW6' );
INSERT INTO dbo.StringTest
VALUES
( 'ZZ_QO3' );
INSERT INTO dbo.StringTest
VALUES
( 'ZZ_TZ7' );
INSERT INTO dbo.StringTest
VALUES
( 'ZZ_UZZ' );
CREATE CLUSTERED INDEX IX_Clustered ON dbo.StringTest (TheString);
/*
Uses fixed % for estimate; 1.1 rows estimated in this case.
Plan for computation:
CSelCalcFixedFilter (0.1) <----
Selectivity: 0.1
*/
ALTER DATABASE MyStringTestDB SET compatibility_level = 130;
GO
SELECT *
FROM dbo.StringTest
WHERE SUBSTRING(TheString, 1, CHARINDEX('_',TheString) - 1) = 'ZZ'
OPTION (QUERYTRACEON 2363, QUERYTRACEON 3604);
/*
Uses histogram to get estimate of 1
CSelCalcPointPredsFreqBased <----
Distinct value calculation:
CDVCPlanLeaf
0 Multi-Column Stats, 1 Single-Column Stats, 0 Guesses
Individual selectivity calculations:
(none)
Loaded histogram for column QCOL: [DBA].[dbo].[StringTest].TheString from stats with id 1
*/
ALTER DATABASE MyStringTestDB SET compatibility_level = 120;
GO
SELECT *
FROM dbo.StringTest
WHERE SUBSTRING(TheString, 1, CHARINDEX('_',TheString) - 1) = 'ZZ'
OPTION (QUERYTRACEON 2363, QUERYTRACEON 3604);
/*
-- Simpler rewrite; works fine in both compat levels and gets better estimate.
SELECT *
FROM dbo.StringTest
WHERE TheString LIKE 'ZZ[_]%'
OPTION (QUERYTRACEON 2363, QUERYTRACEON 3604);
*/
Run Code Online (Sandbox Code Playgroud)
我不知道任何文件。我确实对此进行了调查并进行了一些观察,但是这些观察太长而无法发表评论。
10% 的估计并不总是降级。以下面的例子为例。
TRUNCATE TABLE dbo.StringTest
INSERT INTO dbo.StringTest
SELECT TOP (1000000) 'ZZ_' + LEFT(NEWID(), 12)
FROM master..spt_values v1,
master..spt_values v2;
Run Code Online (Sandbox Code Playgroud)
以及WHERE
你问题中的条款。
WHERE SUBSTRING(TheString, 1, CHARINDEX('_',TheString) - 1) = 'ZZ'
Run Code Online (Sandbox Code Playgroud)
该表包含一百万行。它们都与谓词匹配。在兼容级别 130 下,10% 的猜测产生 100,000 的估计值。低于 120 的估计行数为 1.03913。
120 行为使用直方图但仅用于获取不同行的数量。在我的例子中,密度向量显示为 1.039131E-06,它乘以表基数以获得估计的行数。所有的值实际上都不同,但都与谓词匹配。
跟踪query_optimizer_estimate_cardinality
扩展事件表明,在 130 下有两个不同的<StatsCollection Name="CStCollFilter"
事件。第一个估计有100,000。第二个加载直方图并使用 CSelCalcPointPredsFreqBased/DistinctCountCalculator 获得 1.04 估计值。第二个结果似乎未使用。
您观察到的行为在 130 中并未始终如一地应用。我补充说,我ORDER BY TheString
希望这对 130 估计器来说是一个明显的胜利,因为 120 正在努力争取一行的内存授予,但这个微小的变化足以将估计的行降低到在 130 的情况下也是 1.03913。
添加OPTION (QUERYRULEOFF SelectToFilter)
将进入排序的估计值恢复为 100,000,但内存授予不会增加,并且排序后的估计值仍然基于表的不同值。
类似地,调整并行性的成本阈值以便查询获得并行计划足以在 130 的情况下恢复到较低的估计。添加QUERYTRACEON 8757
也会导致较低的估计值。看起来 10% 的估计只保留用于琐碎的计划。
你提议的重写
WHERE TheString LIKE 'ZZ[_]%'
Run Code Online (Sandbox Code Playgroud)
显示出远优于两者的估计。这个的输出是
CSelCalcTrieBased
Column: QCOL: [MyStringTestDB].[dbo].[StringTest].TheString
Run Code Online (Sandbox Code Playgroud)
显示它使用了try。此处的字符串摘要统计信息部分提供了有关此内容的更多信息。
但是,它与您的原始查询不同。由于_
现在假定第一个实例始终是第三个字符,而不是动态找到。
如果这个假设被硬编码到你的原始查询中
WHERE SUBSTRING(TheString, 1, 3) = 'ZZ_'
Run Code Online (Sandbox Code Playgroud)
估计方法更改为CSelCalcHistogramComparison(INTERVAL)
并且估计的行变得准确。
它能够将其转换为一个范围
WHERE TheString >= 'ZZ_' AND TheString < ???
Run Code Online (Sandbox Code Playgroud)
并使用直方图来估计值在该范围内的行数。
然而,这仅适用于基数估计。LIKE
更可取,因为它可以在运行时使用范围查找。SUBSTRING(TheString, 1, 3)
或LEFT(TheString, 3)
不能。