您现在的位置是:网站首页> 编程资料编程资料

SQL Server中关于基数估计计算预估行数的一些方法探讨_MsSql_

2023-05-26 482人已围观

简介 SQL Server中关于基数估计计算预估行数的一些方法探讨_MsSql_

关于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 2012中执行该SQL语句或者使用查询跟踪标记9481来关闭新的基数评估,数据库优化器使用老的基数评估,你会发现预估行数(Estimated Number of Rows)为135了。如下所示:

 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 OPTION (QUERYTRACEON 9481); GO


这里的计算公式是

 ((Estimated number of rows for first predicate) *(Estimated number of rows for second predicate)) /Total number of rows (0.3*1500)*(0.3*1500)/1500 = 0.09*1500 = 135 

那么现在我们往表TEST_ESTIMATED_ROW里面插入50条记录,此时这个数据量是不会触发统计信息更新的,而此时ID=55的值超出了直方图中的RANG_HI_KY的最大值50,也就是说直方图中没有统计这些新插入的数据,那这种情形称作升序键问题(ascending key problem)。在更统计信息新前就对这些数据运行查询,就会发生此类问题。

 DECLARE @Index INT =1; WHILE @Index <= 50 BEGIN INSERT INTO TEST_ESTIMATED_ROW VALUES(55, 'id is 50'); SET @Index+=1; END GO

那么再来看看下面SQL的预估行数(Estimated Number of Rows),如下所示:

 DBCC FREEPROCCACHE; GO DBCC DROPCLEANBUFFERS; GO SELECT * FROM dbo.TEST_ESTIMATED_ROW WHERE ID = 55; GO


那么预估行数(Estimated Number of Rows)为39.37 是怎么计算来的呢?其实这个问题就是http://www.cnblogs.com/wy123/p/6770258.html这篇博客里面提出的问题,先前++C++他在群里面讨论了一下。

Optimizing Your Query Plans with the SQL Server 2014 Cardinality Estimator里面介绍,这种是基数估计的计算公式为 [All density] * [Rows Sampled] 。但是实际测试发现这个例子并不是如此,那么我们先来亲自测试一下白皮书文档里面的例子(注意,数据库实例是SQL Server 2014,AdventureWorks2012的兼容级别为120),看看文档里面的例子是否正确。

提示:
                    本文由整理自网络,如有侵权请联系本站删除!
                    
本站声明:
1、本站所有资源均来源于互联网,不保证100%完整、不提供任何技术支持;
2、本站所发布的文章以及附件仅限用于学习和研究目的;不得将用于商业或者非法用途;否则由此产生的法律后果,本站概不负责!

-六神源码网