SQL Server中关于基数估计如何计算预估行数的一些探讨
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SQL Server中关于基数估计如何计算预估行数的一些探讨相关的知识,希望对你有一定的参考价值。
关于SQL Server 2014中的基数估计,官方文档Optimizing Your Query Plans with the SQL Server 2014 Cardinality Estimator里有大量细节介绍,但是全部是英文,估计也没有几个人仔细阅读。那么SQL Server 2014中基数估计的预估行数到底是怎么计算的呢? 有哪一些规律呢?我们下面通过一些例子来初略了解一下,下面测试案例仅供参考,如有不足或肤浅的地方,敬请指教!
下面实验测试的环境主要为SQL Server 2014 SP2 (Standard Edition (64-bit)) 具体版本号为12.0.5000.0 ,如有在其它版本测试,后面会做具体说明。如下所示,我们先创建一个测试表并插入一些测试数据后,方便后面的测试工作。
IF EXISTS(SELECT 1 FROM sys.objects WHERE type=\'U\' AND name=\'TEST_ESTIMATED_ROW\')
BEGIN
DROP TABLE TEST_ESTIMATED_ROW;
END
IF NOT EXISTS(SELECT 1 FROM sys.objects WHERE type=\'U\' AND name=\'TEST_ESTIMATED_ROW\')
BEGIN
CREATE TABLE TEST_ESTIMATED_ROW
(
ID INT,
NAME VARCHAR(24)
)
END
GO
DECLARE @Index INT =1;
WHILE @Index <= 100
BEGIN
INSERT INTO TEST_ESTIMATED_ROW
VALUES(10, \'id is 10\');
SET @Index+=1;
END
GO
DECLARE @Index INT =1;
WHILE @Index <= 200
BEGIN
INSERT INTO TEST_ESTIMATED_ROW
VALUES(20, \'id is 20\');
SET @Index+=1;
END
GO
DECLARE @Index INT =1;
WHILE @Index <= 300
BEGIN
INSERT INTO TEST_ESTIMATED_ROW
VALUES(30, \'id is 30\');
SET @Index+=1;
END
GO
DECLARE @Index INT =1;
WHILE @Index <= 400
BEGIN
INSERT INTO TEST_ESTIMATED_ROW
VALUES(40, \'id is 40\');
SET @Index+=1;
END
GO
DECLARE @Index INT =1;
WHILE @Index <= 500
BEGIN
INSERT INTO TEST_ESTIMATED_ROW
VALUES(50, \'id is 50\');
SET @Index+=1;
END
GO
CREATE INDEX IX_TEST_ESTIMATED_ROW_N1 ON TEST_ESTIMATED_ROW(ID);
GO
我们来看看这个表的统计信息以及直方图内容。
DBCC SHOW_STATISTICS (\'dbo.TEST_ESTIMATED_ROW\',\'IX_TEST_ESTIMATED_ROW_N1\');
GO
SQL Server中有两种谓词:过滤谓词和连接谓词 。 我们先来看看过滤谓词的基数估计(预估行数),测试过程,如果要保持测试的公正性或不被其他因素影响,你可以使用下面的DBCC命令来排除干扰,如下例子所示:
DBCC FREEPROCCACHE; --从执行计划缓冲区删除所有缓存的执行计划
GO
DBCC DROPCLEANBUFFERS; --从缓冲池中删除所有缓存,清除缓冲区
GO
SELECT * FROM dbo.TEST_ESTIMATED_ROW WHERE ID = 10;
GO
(注意,执行时请勾选包含实际执行计划按钮)如上所示,预估行数(Estimated Number of Rows)为100,跟实际行数一致。当然你换其他值,例如20, 30, 40 ,50,其预估行数(Estimated Number of Rows)跟实际行数都是正确的(SQL SERVER 2012中测试结果也相同)。那么如果我换一个不存在的值呢?预估行数会是多少呢?
SELECT * FROM dbo.TEST_ESTIMATED_ROW WHERE ID = 4;
GO
如上所示,预估行数(Estimated Number of Rows)为1. 你换其他任何不存在的值,预估行数(Estimated Number of Rows)都为1。这个跟沿用了老的基数评估:超出统计信息范围,那么老的基数评估就认为不存在,评估行数为1。很显然,对于没有超出统计信息范畴的,但是确实不存在的记录,其预估行数(Estimated Number of Rows)也是1,这个基数估计确实是合理,也是正确的。那么如果我使用变量呢?这个预估行数(Estimated Number of Rows)又会是什么值呢?
DECLARE @SID INT = 11; --换任何值都可以
SELECT * FROM dbo.TEST_ESTIMATED_ROW WHERE ID = @SID;
GO
如上截图所示,实际执行计划的预估行数(Estimated Number of Rows)是300, 那么如何计算来的呢? 其实你可以根据公式来计算, 如果不相信,你可以构造各种案例测试验证一下就能得到答案了.
[Row Sampled ]* [ALL density ] = 1500 * 0.2 = 300 也就是统计信息中抽样总行数*All Density(统计信息对象中各列的每个前缀的密度)
如果你加上OPTION(RECOMPILE), 那么预估行数(Estimated Number of Rows)又会变成1
DECLARE @SID INT = 11;
SELECT * FROM dbo.TEST_ESTIMATED_ROW WHERE ID = @SID OPTION(RECOMPILE)
GO
如果你赋予@SID值为20,并加上OPTION(RECOMPILE)时,那么预估行数(Estimated Number of Rows)就会变成EQ_ROWS的值了
DECLARE @SID INT = 20;
SELECT * FROM dbo.TEST_ESTIMATED_ROW WHERE ID = @SID OPTION(RECOMPILE)
GO
接下来,我们修改一下SQL语句,将查询条件从等于符号改为大于符号,如下所示:
DECLARE @SID INT = 11;
SELECT * FROM dbo.TEST_ESTIMATED_ROW WHERE ID > @SID;
GO
如上所示,预估行数(Estimated Number of Rows)变为了450,那么这个值是怎么计算得来的呢?
计算公式是: [Row Sampled ] * 0.3(30%)
1500 *0.3= 450
肯定会有人问,你怎么知道是 [Row Sampled ] * 0.3 呢? 不会是你逆推的吧。 不错,这里是一个推测(网上也有不少资料都确认是0.3,权且当做计算公式中的一个常量),而且也做了不少测试,确实就是30%。例如你将@SID赋值为41,预估行数(Estimated Number of Rows)依然为450,如果你怀疑是缓存的执行计划缘故,你可以先清空缓存的执行计划,结果依然如此。根据我的测试,不管你给@SID赋予任何值,预估行数(Estimated Number of Rows)全部为450
DBCC FREEPROCCACHE;
GO
DBCC DROPCLEANBUFFERS;
GO
DECLARE @SID INT = 41;
SELECT * FROM dbo.TEST_ESTIMATED_ROW WHERE ID > @SID;
GO
如果SQL加上 OPTION(RECOMPILE) ,然后@SID赋予RANGE_HI_KEY里的值,那么预估行数(Estimated Number of Rows)又是如何计算的呢?
DECLARE @SID INT = 20;
SELECT * FROM dbo.TEST_ESTIMATED_ROW WHERE ID > @SID OPTION(RECOMPILE);
GO
这个1200 是这样计算的,如下所示,大于20的RANGE_HI_KEY有30 , 40, 50 ,他们对应的EQ_ROWS值相加 300+ 400 + 500 =1200, 不信你可以测试一下,将@SID赋予30,那么预估行数(Estimated Number of Rows)就会变成900.
那么我们再修改一下SQL查询语句,例如,我们要做一个区间查询,预估行数(Estimated Number of Rows)又会有什么变化呢?
DBCC FREEPROCCACHE;
GO
DBCC DROPCLEANBUFFERS;
GO
DECLARE @Min_Value INT = 20;
DECLARE @Max_Value INT = 50;
SELECT * FROM dbo.TEST_ESTIMATED_ROW WHERE ID > @Min_Value AND ID < @Max_Value
GO
如上所示,预估行数(Estimated Number of Rows)为246.475 这个值怎么来的呢?其实它是这样计算的:
Selectivity of most selective predicate * Square root of (selectivity of second most selective predicate) * Total number of rows
SELECT 0.3*SQRT(0.3)*1500 --246.475150877325 --0.3是计算规则里面的一个常量
以上是关于SQL Server中关于基数估计如何计算预估行数的一些探讨的主要内容,如果未能解决你的问题,请参考以下文章