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


26

我有一个带有字符串列和谓词的表,该谓词检查具有一定长度的行。在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

这是显示更多测试的更完整的脚本

我还阅读了有关SQL Server 2014基数估计器白皮书,但没有发现任何可以澄清这种情况的内容。

Answers:


20

对于旧版CE,我看到的估计值为3.16228%的行–这是用于column =文字谓词的“幻数”启发式方法(还有其他基于谓词构造的启发式方法,但LEN用于该列的换行符旧版CE结果与此猜测框架相符)。您可以乔·萨克(Joe Sack)撰写的《选择性猜测》(Selectivity Guesses)上没有统计的文章和伊恩·何塞(Ian Jose)的《恒定常数比较估计》的文章中看到此类示例。

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

现在,对于新的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

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

可能的解决方法

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

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

像计划


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

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

13

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

我认为@Zane的答案涵盖了这一部分。

有没有好的解决方法?

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

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

  • 当未在其上定义索引时,将在“非持久计算列”上自动创建统计信息。
  • 在“计算列”上添加非聚集索引不仅无济于事,而且实际上会稍微损害性能。CPU和经过时间略高。估算成本略高(无论价值多少)。
  • 将“计算列”设置为PERSISTED(无索引)要比其他两个变体更好。估计的行数更准确。CPU和经过的时间更好(如预期的那样,因为它不必每行计算任何内容)。
  • 我无法在计算列上创建过滤索引或过滤统计信息(由于正在计算),即使它是PERSISTED:-(

1
感谢您对持久与否的彻底比较。很高兴知道,即使持久化计算列具有其优势,在某些情况下表达式的统计信息是有益的情况下,非持久化也可以以很少的开销很快赢得胜利。
2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.