前提html
本文仅讨论SQL Server查询时,
对于非复合统计信息,也即每一个字段的统计信息只包含当前列的数据分布的状况下,
在用多个字段进行组合查询的时候,如何根据统计信息去预估行数的。
利用不一样字段的统计信息作数据行数预估的算法原理,以及SQL Server 2012和SQL Server 2014该算法的差别状况,
这里暂时不涉及复合统计信息,暂不涉及统计信息的更新策略及优化相关话题,以及其余SQL Server版本计算方式。
算法
统计信息是什么数据库
简单说就是对某些字段的数据分布的一种描述,让SQL Server在根据条件作查询的时候,大概知道预期的数据大小,
从而指导生成合理执行计划的一种数据库对象ide
统计信息的分类函数
索引上会自动建立统计信息,SQL Server也会根据具体的查询,在某些非索引自动建立索引,固然也能够经过手动方式建立统计信息。
先来直观地了解一下统计信息长什么样,参考截图,就是这么个样子,
_WA_Sys_****开头的是系统根据须要建立的统计信息,
与索引同名的是索引上建立的统计信息,
手动建立统计信息也能够在知足SQL Server命名要求的状况下自行命名。oop
下面一个是索引的统计信息。性能
统计信息的做用测试
查询引擎根据统计信息提供的数据作出合理的执行计划。
那么,查询引擎到底是怎么利用统计信息作预估的呢,
以及下面将要提到的SQL Server 2014中较以前的版本有哪些变化?
本文将对此两点作一个简单的分析来讲明SQL Server是怎么根据统计信息作估算的,下面开始正文。优化
测试环境搭建spa
习惯性地作一个演示的环境,建立一个表,写入100W的数据后面测试用。
create table TestStatistics ( Id int identity(1,1), Status1 int, Status2 int, Status3 int )
insert into TestStatistics values (RAND()*1000,RAND()*250,RAND()*50) go 1000000
表中有四个字段,第一个是自增列,主要看Status1,Status2,Status3这三个字段,
三个字段的取值都是用随机数乘以一个常量系数的出来的,
所以这三个字段的数据分布范围分别是
Status1:0-999(1000种数据分布)
Status2:0-249(250种数据分布)
Status3:0-49(50种数据分布)
这个后面有用。
首先在SQL Server 2012中作测试
先作这么一个查询:select * from TestStatistics where Status1=885 and Status2=88 and Status3=8
这个查询完成以后,表上自动建立一个三个统计信息,
这三个统计信息分别是Status1,Status2,Status3这个三个字段的数据分布描述
首先来看一下其中这个_WA_Sys_00000002_0EA330E9,也即Status1这个列的统计信息的详细信息,
注意All density字段值,选择性是反应一个表中该字段的重复数据有多少或者说惟一性有多少,
计算方法是:1/表中该字段非重复个数。
上面说了,这个Status1这个列的取值范围是0-999,一共有1000中取值可能行,
那么这个选择行就是1/1000=0.001,因此也是吻合这里的All density=0.001的
照这么计算,其他两个字段的选择度分别是1/250=0.004 和1/50=0.02,分别以下截图的 All density。
执行计划对数据行的预估
说完统计信息的基础问题以后,咱们就能够来观察执行计划对目标数据的预估规律了。
咱们来看这么一个查询,以下,注意这个是查询的条件是参数变量,而不是直接的值,后面我会解释为何这么作。
来观察执行计划对数据行的预估:能够看出来,预估为4行。
那么这个4行是怎么计算出来的呢?
这就要利用到咱们上面的选择性了,
Status1字段的选择性是0.001,Status2的选择性是0.04,
在SQL Server 2012中,对数据行的预估计算方式是各个字段的选择性的乘积,
假如Pn表明不一样字段的选择性,那么预估行数的计算方法就是: 预估行数=p0*p1*p2*p3……*RowCount
所以,执行计划显示的:预估行数=0.001*0.004*总行数(也即1000000)= 4
说到这里解释两个可能存在的几个疑问:
第一,上述示例是用两个字段查询的,为何不拿三个字段作演示说明?
首选,不论是多少个字段查询,预估行数符合上述计算方式是没有问题的,
可是若是经过上述公式计算出来的结果很是小,在少于1的状况下,SQL Server显示预估为1行。
按照上述计算方法,用三个字段作查询,
预估行数=0.001*0.004*0.02*总行数(也即1000000)= 0.08<1,因此预估为1行。
第二,为何不直接用值查询,而是用变量作查询?
熟悉SQL Server的同窗应该都知道,直接用变量查询的时候,SQL Server编译的时候不知道具体的参数值,
在不知道具体参数值的状况下,它是使用字段的选择性的时候是用到通常性(或者说是平均)的值,
也就是统计信息中总体计算出来字段的选择性,也即All density=0.001
这里暂定认为数据分布是均匀的,也即每一个值分布差异不大。
但事实上每一个值的分布的差异还有存在的,
尤为是分布不均匀的时候,固然这个是另一个很是大的话题了,这里暂不讨论。
若是直接用明确的值作查询。
好比 select * from TestStatistic where Status1=885 and Status2=88
SQL Server会根据统计信息中每一个字段 :Status1=885 的行数和 Status2=88行数的具体的值,
利用上述公式作预估
那么就继续用具体的值作演示说明,
能够直接用where Status1=885 and Status2=88这个条件查询来观察预估结果。
首先咱们看统计信息中Status1=885 的分布行数,1079行
而后再看统计信息中Status2=88 的分布行数,3996行
利用上述公式,预估行数为4.31168行
那么直接利用值作查询是否是这个预估的行数呢?直接上图,完美地吻合了上述的计算方法获得的结果。
第三,没有索引的状况下是符合预估的计算方法,若是建立了索引呢?
查询条件中的各个列的统计信息是非相关的,
若是分别在各个列上建立单个列的索引信息,在查询的时候也属于非相关统计信息。
如截图,也就是说,虽然建立了索引,执行计划发生了变化,
从一开始的表扫描变成了经过两个索引查找后作hash join,而后Loop join查询数据,咱无论它就是变成什么执行计划了
可是统对数据的预估仍是跟上面全表扫描同样的,都是预估为4.31168,没有由于建立了索引以及执行计划发生了变化而改变(预估行数)。
由于即使是建立了单列上的索引,执行计划变了,可是统计信息仍是非相关的,也就是一个统计信息只描述一列字段的分布状况。
而后在SQL Server 2014中作测试
上述一样的数据,我这里经过link server 将上述SQL Server 2012实例下的测试表的结果导入到SQL Server 2014的实例下的表中。
如今表结构和数据彻底一致。
首选,作一个一样的测试,利用两个变量查询的查询条件作查询,看看SQL Server 2014预估的算法有什么变化。
还记得上面在 SQL Server 2012中一样的写法,一样的数据的预估的状况吧,刚才预估的是4行,如今怎么变成63.2456行了?
预估行数的计算公式变了吗,固然变了,这正是本文要说的重点。
那么SQL Server 2014中是怎么预估的呢?公式是这么来的:预估行数 = P0*P11/2 * P21/4 * P31/8……* RowCount
那么来根据此计算方式来计算预估行数的问题:预估行数=0.001*0.0041/2*1000000 = ?
这里我就不作开方运算了,拿来主义,直接用SQL Server来算拉倒了,SQL Server给咱们提供了一个开方函数(SQRT),真JB好用。
计算一下结果吧,
没错,是63.24555,保留四位有效数字的话就是63.2456了,预估行数跟上面计算出来的结果也是彻底吻合的。
补充测试1:
一样地,用三个条件作查询,预估算法也一样复合上述公式的结果。
按照公式来计算预估行数,选择性按照总体计算出来的选择性来,一样也是吻合的。
补充测试2:
若是把查询条件换作具体的值,跟在SQL Server 2012中同样,SQL Server2014 也一样会根据具体的值得数据作计算
进行这么个查询:select * from TestStatistics2014 where Status1=858 and Status2=88
解释一下为何此次Status1换成858了:
由于即使表结构,数据彻底一致吧,受限于统计信息的步长(Steps)只有200,两个库的统计信息也不彻底一致,统计信息不能精确到任何一个值,
咱们这里为了演示这个算法,找一个具体的RANGE_HI_KEY值,比较容易说明问题。
首先看Status1=858的数据分布状况
再看Status2=88的数据分布状况
利用上述计算方法计算出来的预估:63.27713
执行计划的预估:63.27713,也是彻底吻合的。
补充测试3,在查询列上建立建立单独的索引
跟SQL Server 2012中同样,执行计划发生了变化 ,可是对于数据行的预估,一样并无由于执行计划的变化而(预估行数)变化。
虽然执行计划变了,可是对数据的预估并无变化,预估的算法仍是符合:预估行数 = P0*P11/2 * P21/4 * P31/8……* RowCount
在此能够看出,执行计划对于(未超过统计信息范围的状况下)数据行的预估,是有必定规律的,
这个规律就是:
SQL Server 2012 中,预估行数=p0*p1*p2*p3……*RowCount(Pn为查询字段的选择性),
SQL Server 2014 中,预估行数= P0*P11/2 * P21/4 * P31/8……* RowCount(Pn为查询字段的选择性)。
固然若是说统计信息过时或者取样密度不够,那就另当别论了,这个就关系到统计信息的更新策略问题了,也是一个很是大并且很是现实的问题,暂不深刻展开讨论。
因此一开始我说暂不考虑统计信息自身是否理想,这里是在统计信息很是完整的状况下作测试的。
微软为何在SQL Server 2014中,对非相关且未超出统计信息范围的预估行数算法作这么一个变化,
由于PN的值是小于1的
预估行数的计算方法从p0*p1*p2*p3……*RowCount变化为P0*P11/2 * P21/4 * P31/8……* RowCount,显然是增长了预估行数的大小,
同时本文未说起的另一种状况:对于超出统计信息范围的状况下,新的预估方法也增长预估行数的大小,
从总体上看,算法是倾向于"估多不估少”的,有这么一个改变
至于为何要作出这个改变?
若是常常作SQL优化的就会发现,很多问题都是少估了预期的数据行数(由于种种缘由吧,这里暂时不讨论为何少估),
形成执行SQL时分配的资源不够,从而拖慢了SQL的执行效率
一个很是典型的问题就是,预估的数据比实际的数据行数小,形成好比内存授予的不够大,以及实际运算过程当中采用不合理的执行计划
我的认为,(控制在必定范围以内的)估多的状况下能够经过获取更多的系统资源来提高SQL的执行效率,
正常状况下也不会说是跟实际值差的太离谱形成资源的浪费。
固然也有特殊状况,那就另当别论
要注意的是我这里有个前提,非相关的统计信息,不论是没有任何索引,仍是是建立和单列上的索引,对应的统计信息,都属于非相关统计信息,
若是建立复合索引(有人习惯叫组合索引),那么执行计划对于数据行的预估并不符合上述算法,具体算法我也不清楚。
此种状况下,在SQL Server 2012和SQL Server 2014中预估算法也不同,这个有机会再研究吧。
对于测试结果的补充说明:
测试过程当中必定要保证统计信息的完整性,以及取样的百分比问题,理性状况下都是按照100%取样的,
中间我略去了一些细节问题,好比没此测试以前都会 update statistics TestStatistic with fullscan,保证100%取样。
既然要精确到小数点后几位,固然要求条件是理想状况下的,目的就是必定要排除其余条件对测试结果的影响。
总结:
本文经过一个简单的示例,来了解了SQL Server经过统计信息对数据预估的计算方式和原理,以及SQL Server 2012和SQL Server2014之间的差别。
统计信息对于SQL执行计划的选择起着中枢神经般的做用,不光是在SQL Server数据库中,包括其余关系数据库,统计信息都是一个很是重要的数据库对象。
能够说,SQL优化,统计信息以及与之息息相关的执行计划是一个很是重要的因素,了解统计信息方面的知识对性能调优有着很是重要的做用。
在涉及到组合索引上的统计信息状况下,执行计划对数据行的预估,SQL Server2012和SQL Server 2014中也不同,问题将会更加有趣,待有时间再写吧。
参考:Fanr_Zh 大神的 http://www.cnblogs.com/Amaranthus/p/3678647.html