前提

      本文仅讨论SQL Server查询时,
    对于非复合统计信息,也即每个字段的统计信息只包含当前列的数据分布的情况下,
    在用多个字段进行组合查询的时候,如何根据统计信息去预估行数的。
    利用不同字段的统计信息做数据行数预估的算法原理,以及SQL Server 2012和SQL Server 2014该算法的差异情况,
    这里暂时不涉及复合统计信息,暂不涉及统计信息的更新策略及优化相关话题,以及其他SQL Server版本计算方式。
  

统计信息是什么

    简单说就是对某些字段的数据分布的一种描述,让SQL Server在根据条件做查询的时候,大概知道预期的数据大小,
    从而指导生成合理执行计划的一种数据库对象

统计信息的分类

     索引上会自动创建统计信息,SQL Server也会根据具体的查询,在某些非索引自动创建索引,当然也可以通过手动方式创建统计信息。
     先来直观地了解一下统计信息长什么样,参考截图,就是这么个样子,
     _WA_Sys_****开头的是系统根据需要创建的统计信息,
    与索引同名的是索引上创建的统计信息,
    手动创建统计信息也可以在满足SQL Server命名要求的情况下自行命名。

  

  下面一个是索引的统计信息。

    

统计信息的作用

    查询引擎根据统计信息提供的数据做出合理的执行计划。
    那么,查询引擎究竟是怎么利用统计信息做预估的呢,
    以及下面将要提到的SQL Server 2014中较之前的版本有哪些变化?
    本文将对此两点做一个简单的分析来说明SQL Server是怎么根据统计信息做估算的,下面开始正文。

    测试环境搭建

  习惯性地做一个演示的环境,创建一个表,写入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

          以及 http://msdn.microsoft.com/en-us/library/dn673537.aspx

SQL Server 执行计划利用统计信息对数据行的预估原理以及SQL Server 2014中预估策略的改变的更多相关文章

  1. SQL Server 执行计划利用统计信息对数据行的预估原理二(为什么复合索引列顺序会影响到执行计划对数据行的预估)

    本文出处:http://www.cnblogs.com/wy123/p/6008477.html 关于统计信息对数据行数做预估,之前写过对非相关列(单独或者单独的索引列)进行预估时候的算法,参考这里. ...

  2. sqlplus中显示sql执行计划和统计信息

    31 ,32 , 33 ,34  keywords : oracle  storage  structure 最详细讲解: 1:doc 1   logical  storage structure 2 ...

  3. 为准确生成执行计划更新统计信息-analyze与dbms_stats

    如果我们想让CBO利用合理利用数据的统计信息,正确判断执行任何SQL查询时的最快途径,需要及时的使用analyze命令或者dbms_stats重新统计数据的统计信息. 例如索引跳跃式扫描(INDEX ...

  4. Oracle执行计划与统计信息的一些总结

    [日期:2011-08-05]来源:Linux社区  作者:wangshengfeng1986211[字体:大 中 小] 2010-07-01 15:03 1.SET AUTOTRACE ON EXP ...

  5. SQL Server 执行计划缓存

    标签:SQL SERVER/MSSQL SERVER/数据库/DBA/内存池/缓冲区 概述 了解执行计划对数据库性能分析很重要,其中涉及到了语句性能分析与存储,这也是写这篇文章的目的,在了解执行计划之 ...

  6. 浅析SQL SERVER执行计划中的各类怪相

    在查看执行计划或调优过程中,执行计划里面有些现象总会让人有些疑惑不解: 1:为什么同一条SQL语句有时候会走索引查找,有时候SQL脚本又不走索引查找,反而走全表扫描? 2:同一条SQL语句,查询条件的 ...

  7. 引用:初探Sql Server 执行计划及Sql查询优化

    原文:引用:初探Sql Server 执行计划及Sql查询优化 初探Sql Server 执行计划及Sql查询优化 收藏 MSSQL优化之————探索MSSQL执行计划 作者:no_mIss 最近总想 ...

  8. SQL Server 执行计划解析

    前置说明: 本文旨在通过一个简单的执行计划来引申并总结一些SQL Server数据库中的SQL优化的关键点,日常总结,其中的概念介绍中有不足之处有待补充修改,希望大神勘误. SQL语句如下: SELE ...

  9. sql server 执行计划(execution plan)介绍

    大纲:目的介绍sql server 中执行计划的大致使用,当遇到查询性能瓶颈时,可以发挥用处,而且带有比较详细的学习文档和计划,阅读者可以按照我计划进行,从而达到对执行计划一个比较系统的学习. 什么是 ...

随机推荐

  1. Nginx 配置从零开始

    作为一个 nginx 的初学者记录一下从零起步的点滴. 基本概念 Nginx 最常的用途是提供反向代理服务,那么什么反向代理呢?正向代理相信很多大陆同胞都在这片神奇的土地上用过了,原理大致如下图: 代 ...

  2. 怎么可以让div自适应屏幕的高度?(已解决)

    主要解决问题的方法是用JS脚本. 先看布局, 一个div是首部,另一个div是主体,主体包含左侧菜单和右侧内容. 我想把主体div的高度自适应屏幕剩余区域,怎么做? 首先,获取可见区域的高度,docu ...

  3. C语言中的栈和堆

    原文出处<http://blog.csdn.net/xiayufeng520/article/details/45956305#t0> 栈内存由编译器分配和释放,堆内存由程序分配和释放. ...

  4. Java演算法之堆排序(HeapSort)

    import java.util.Arrays; publicclass HeapSort { inta[]={49,38,65,97,76,13,27,49,78,34,12,64,5,4,62,9 ...

  5. 跨平台移动开发UI语言 -XAML

    Xamarin.Forms 把XAML (Extensible Application Markup Language, XAML) 带进了ios,android的界面开发,也就使得使用Xamarin ...

  6. ASP.NET MVC 和 Ruby 相结合的Web框架Oak

    在http://www.asp.net/mvc/open-source 上有个项目Oak: Frictionless development for ASP.NET MVC single page w ...

  7. Microsoft Azure Web Sites应用与实践【2】—— 通过本地IIS 远程管理Microsoft Azure Web Site

    Microsoft Azure Web Sites应用与实践 系列: [1]—— 打造你的第一个Microsoft Azure Website [2]—— 通过本地IIS 远程管理Microsoft ...

  8. 我所记录的git命令(非常实用)

    一.前言 记录一下工作中常用到的git命令,只是简单的笔记,欢迎大家交流... [ 顺便问下园友们,怎么感觉博客园发布的博客搜索有时都搜不到,后台编辑能填的都填写了,还是觉得搜索排名不高? 相同的标题 ...

  9. entityframework使用CodeFirst创建MySql数据库出错的解决方法恢复

    先告诉大家一个秘密,EF在使用 update-database 时候,使用的连接字符串来自于解决方案中的“启动项目”,而不是你在包管理器中选择的“默认项目” 0x01. 先说错误,方便大家检索到 开发 ...

  10. TODO:Node.js pm2使用方法

    TODO:Node.js pm2使用方法 pm2 是一个带有负载均衡功能的Node应用的进程管理器. 当你要把你的独立代码利用全部的服务器上的所有CPU,并保证进程永远都活着,0秒的重载, PM2是完 ...