SQL Server 执行计划利用统计信息对数据行的预估原理以及SQL Server 2014中预估策略的改变
前提
本文仅讨论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中预估策略的改变的更多相关文章
- SQL Server 执行计划利用统计信息对数据行的预估原理二(为什么复合索引列顺序会影响到执行计划对数据行的预估)
本文出处:http://www.cnblogs.com/wy123/p/6008477.html 关于统计信息对数据行数做预估,之前写过对非相关列(单独或者单独的索引列)进行预估时候的算法,参考这里. ...
- sqlplus中显示sql执行计划和统计信息
31 ,32 , 33 ,34 keywords : oracle storage structure 最详细讲解: 1:doc 1 logical storage structure 2 ...
- 为准确生成执行计划更新统计信息-analyze与dbms_stats
如果我们想让CBO利用合理利用数据的统计信息,正确判断执行任何SQL查询时的最快途径,需要及时的使用analyze命令或者dbms_stats重新统计数据的统计信息. 例如索引跳跃式扫描(INDEX ...
- Oracle执行计划与统计信息的一些总结
[日期:2011-08-05]来源:Linux社区 作者:wangshengfeng1986211[字体:大 中 小] 2010-07-01 15:03 1.SET AUTOTRACE ON EXP ...
- SQL Server 执行计划缓存
标签:SQL SERVER/MSSQL SERVER/数据库/DBA/内存池/缓冲区 概述 了解执行计划对数据库性能分析很重要,其中涉及到了语句性能分析与存储,这也是写这篇文章的目的,在了解执行计划之 ...
- 浅析SQL SERVER执行计划中的各类怪相
在查看执行计划或调优过程中,执行计划里面有些现象总会让人有些疑惑不解: 1:为什么同一条SQL语句有时候会走索引查找,有时候SQL脚本又不走索引查找,反而走全表扫描? 2:同一条SQL语句,查询条件的 ...
- 引用:初探Sql Server 执行计划及Sql查询优化
原文:引用:初探Sql Server 执行计划及Sql查询优化 初探Sql Server 执行计划及Sql查询优化 收藏 MSSQL优化之————探索MSSQL执行计划 作者:no_mIss 最近总想 ...
- SQL Server 执行计划解析
前置说明: 本文旨在通过一个简单的执行计划来引申并总结一些SQL Server数据库中的SQL优化的关键点,日常总结,其中的概念介绍中有不足之处有待补充修改,希望大神勘误. SQL语句如下: SELE ...
- sql server 执行计划(execution plan)介绍
大纲:目的介绍sql server 中执行计划的大致使用,当遇到查询性能瓶颈时,可以发挥用处,而且带有比较详细的学习文档和计划,阅读者可以按照我计划进行,从而达到对执行计划一个比较系统的学习. 什么是 ...
随机推荐
- MySQL 数据库主从复制架构
前文<MySQL 数据库事务与复制>分析了 MySQL 复制过程中如何保证 binlog 和事务数据之间的一致性,本文进一步分析引入从库后需要保证主从的数据一致性需要考虑哪些方面. 原生复 ...
- TaintDroid剖析之IPC级污点传播
TaintDroid剖析之IPC级污点传播 作者:简行.走位@阿里聚安全 前言 在前三篇文章中我们详细分析了TaintDroid对DVM栈帧的修改,以及它是如何在修改之后的栈帧中实现DVM变量级污点跟 ...
- ReactJS入门(四)—— 组件API
本篇将介绍 React 组件的API,其中主要的几个API我们在第一篇的时候便已介绍过,这里可以做个温故知新. 本篇的代码你也可以在我的Github上获取到. setState 参数: nextSta ...
- ENode 2.6 架构与设计简介以及全新案例分享
前言 ENode是一个应用开发框架,为开发人员提供了一整套基于DDD+CQRS+ES+EDA架构风格的解决方案.ENode从发布1.0开始到现在的差不多两年时间,我几乎每周都在更新设计或实现代码.以至 ...
- Lesson 8 The best and the worst
Text Joe Sanders has the most beautiful garden in our town. Nearly everbody enters for 'The Nicest G ...
- CSharpGL(0)一个易学易用的C#版OpenGL
+BIT祝威+悄悄在此留下版了个权的信说: CSharpGL(0)一个易学易用的C#版OpenGL CSharpGL是我受到SharpGL的启发,在整理了SharpGL,GLM,SharpFont等开 ...
- Underscore.js使用
Underscore 是一个 JavaScript 工具库,它提供了一整套函数式编程的实用功能,但是没有扩展任何 JavaScript 内置对象. 他解决了这个问题:"如果我面对一个空白的 ...
- 前段播放 流媒体(RTMP,RTSP,HLS)
前言 最近项目需要流媒体的播放,后端一共提供了 三种流数据(RTSP,RTMP,HLS),在不同的场景可能会使用到不同方式播放,就需要做到适配, 支持所有的流数据播放.花了一段时间研究,在这里和大家分 ...
- int and string
int转string一.#include <sstream> int n = 0; std::stringstream ss; std::string str; ss<<n; ...
- 解决ora-01652无法通过128(在temp表空间中)扩展temp段的过程
解决ora-01652无法通过128(在temp表空间中)扩展temp段的过程 昨天开发人员跟我说,执行一个sql语句后,大约花了10分钟,好不容易有一个结果,但是报了一个ora-01652错误,查阅 ...