为什么 LEN() 函数严重低估了 SQL Server 2014 中的基数?

Geo*_*son 26 sql-server varchar functions sql-server-2014 cardinality-estimates

我有一个带有字符串列的表和一个检查具有特定长度的行的谓词。在 SQL Server 2014 中,无论我检查的长度如何,我都会看到估计为 1 行。这产生了非常糟糕的计划,因为实际上有数千甚至数百万行,而 SQL Server 选择将此表放在嵌套循环的外侧。

对于 SQL Server 2014 的基数估计为 1.0003 而 SQL Server 2012 估计为 31,622 行,是否有解释?有没有好的解决方法?

以下是该问题的简短再现:

-- Create a table with 1MM rows of dummy data
CREATE TABLE #customers (cust_nbr VARCHAR(10) NOT NULL)
GO

INSERT INTO #customers WITH (TABLOCK) (cust_nbr)
    SELECT TOP 1000000 
        CONVERT(VARCHAR(10),
        ROW_NUMBER() OVER (ORDER BY (SELECT NULL))) AS cust_nbr
    FROM master..spt_values v1
    CROSS JOIN master..spt_values v2
GO

-- Looking for string of a certain length.
-- While both CEs yield fairly poor estimates, the 2012 CE is much
-- more conservative (higher estimate) and therefore much more likely
-- to yield an okay plan rather than a drastically understimated loop join.
-- 2012: 31,622 rows estimated, 900K rows actual
-- 2014: 1 row estimated, 900K rows actual
SELECT COUNT(*)
FROM #customers
WHERE LEN(cust_nbr) = 6
OPTION (QUERYTRACEON 9481) -- Optionally, use 2012 CE
GO
Run Code Online (Sandbox Code Playgroud)

这是一个更完整的脚本,显示了其他测试

我还阅读了SQL Server 2014 Cardinality Estimator 上白皮书,但没有找到任何说明情况的内容。

Zan*_*ane 20

对于旧版 CE,我看到估计值是 3.16228% 的行——这是用于列 = 字面谓词的“幻数”启发式(还有其他基于谓词构造的启发式方法——但LEN环绕列的旧版 CE 结果与此猜测框架匹配)。您可以Joe Sack撰写的关于Selectivity Guesses in without Statistics和Ian Jose撰写的Constant-Constant比较估计的帖子中看到此类示例。

-- Legacy CE: 31622.8 rows
SELECT  COUNT(*)
FROM    #customers
WHERE   LEN(cust_nbr) = 6
OPTION  ( QUERYTRACEON 9481); -- Legacy CE
GO
Run Code Online (Sandbox Code Playgroud)

现在对于新的 CE 行为,看起来优化器现在可以看到它(这意味着我们可以使用统计信息)。我通过查看下面的计算器输出进行了练习,您可以将相关的自动生成统计信息视为指针:

-- New CE: 1.00007 rows
SELECT  COUNT(*)
FROM    #customers
WHERE   LEN(cust_nbr) = 6
OPTION  ( QUERYTRACEON 2312 ); -- New CE
GO

-- View New CE behavior with 2363 (for supported option use XEvents)
SELECT  COUNT(*)
FROM    #customers
WHERE   LEN(cust_nbr) = 6
OPTION  (QUERYTRACEON 2312, QUERYTRACEON 2363, QUERYTRACEON 3604, RECOMPILE); -- New CE
GO

/*
Loaded histogram for column QCOL:
[tempdb].[dbo].[#customers].cust_nbr from stats with id 2
Using ambient cardinality 1e+006 to combine distinct counts:
  999927
 
Combined distinct count: 999927
Selectivity: 1.00007e-006
Stats collection generated:
  CStCollFilter(ID=2, CARD=1.00007)
      CStCollBaseTable(ID=1, CARD=1e+006 TBL: #customers)
 
End selectivity computation
*/
 
EXEC tempdb..sp_helpstats '#customers';


--Check out AVG_RANGE_ROWS values (for example - plenty of ~ 1)
DBCC SHOW_STATISTICS('tempdb..#customers', '_WA_Sys_00000001_B0368087');
--That's my Stats name yours is subject to change
Run Code Online (Sandbox Code Playgroud)

不幸的是,该逻辑依赖于对不同值数量的估计,而不会针对LEN函数的效果进行调整。

可能的解决方法

您可以通过将 重写LEN为 a来获得两种 CE 模型下基于 trie 的估计LIKE

SELECT COUNT_BIG(*)
FROM #customers AS C
WHERE C.cust_nbr LIKE REPLICATE('_', 6);
Run Code Online (Sandbox Code Playgroud)

喜欢计划


有关使用的跟踪标志的信息:

  • 2363:显示很多信息,包括正在加载的统计信息。
  • 3604:将 DBCC 命令的输出打印到消息选项卡。


Sol*_*zky 13

对于 SQL 2014 的基数估计为 1.0003 而 SQL 2012 估计为 31,622 行,是否有解释?

我认为@Zane 的回答很好地涵盖了这一部分。

有没有好的解决方法?

您可以尝试为该计算列创建一个非持久化计算列,LEN(cust_nbr)并(可选)在该计算列上创建一个非聚集索引。这应该为您提供准确的统计数据。

我做了一些测试,这是我发现的:

  • 当非持久计算列上没有定义索引时,统计信息是自动创建的。
  • 在 Computed Column 上添加非聚集索引不仅没有帮助,反而会稍微影响性能。CPU 和运行时间略高。估计成本略高(无论值多少钱)。
  • 将计算列设为PERSISTED(无索引)比其他两个变体更好。估计行数更准确。CPU 和运行时间更好(正如预期的那样,因为它不必每行计算任何内容)。
  • 我无法在计算列上创建过滤索引或过滤统计信息(因为它正在计算),即使它是PERSISTED:-(