Quantcast
Channel: CodeSection,代码区,SQL Server(mssql)数据库 技术分享 - CodeSec
Viewing all articles
Browse latest Browse all 3160

[原]全废话SQL Server统计信息(2)――统计信息基础

$
0
0

接上文: http://blog.csdn.net/dba_huangzj/article/details/52835958

我想在大地上画满窗子,让所有习惯黑暗的眼睛都习惯光明――顾城《我是一个任性的孩子》

这一节主要介绍一些理论层面的东西,主要针对SQL Server,为后面的做铺垫,如果从实操层面考虑可以跳过,但是我强烈建议还是要找时间看一下这节。本节的内容如下:

SQL Server统计信息 列级统计信息 统计信息与执行计划 统计信息与内存分配 开销预估模型 SQL Server统计信息

说到统计信息,就一定要提到查询优化器,主流关系型数据库管理系统的查询优化器都是基于开销的优化(cost-based optimizer, CBO),而优化器是生成执行计划的组件,所以执行计划的质量直接依赖于开销预估的准确性,同样,执行计划的预估开销又基于算法/操作符的使用和基数预估。所以,为了让优化器得到准确的预估开销,优化器需要尽可能准确地预估制定查询要返回的记录数。

在查询被优化的过程中,SQLServer会分析很多候选执行计划,并预估它们的相对开销,然后选择最高效的那个执行计划。因此,不准确的基数和开销预估会引起优化器选择不高效的执行计划从而影响数据库性能。

这里提到的开销、基数等概念,在用户层面看来,就是统计信息,或者说,对于用户来说,这些信息中可控部分主要是统计信息。

SQL Server的统计信息包含三个主要部分:直方图(histogram)、密度信息(density information)和字符串统计信息(string statistics),这三个部分在基数预估过程中分别协助不同的部分。

提醒:SQL Server在统计信息中存储了一个额外的针对字符串值的信息,称为Trie Trees(直译叫字典树或前缀树),这个信息可以针对字符串键值提供更好基数预估。但是这部分属于“未公开功能”,所以不在这里介绍。

SQL Server 创建和维护统计信息,通过提供基数预估帮助优化器分析。而基数预估是对一个查询,“假设”使用了某些筛选条件、JOIN联接或GROUP BY 操作之后,会返回的记录数。而另一个常见术语选择度(Selectivity)的概念和基数预估很类似,它计算满足谓词的行在表中的百分比,选择度越高,返回的结果越小。 提醒一下,选择度是索引键值选择的重要指标之一。

最后,我们来回答一下一个一直没有正式回答的问题:为什么我们需要统计信息?答案其实很简单,但是可能需要有过一定的经历,才会深有体会,这个答案就是统计信息降低了在优化过程中必须分析的数据量,如果优化器每次优化都要访问实体表/索引的话,分析过程会变得非常低效。所以优化器会使用实际数据的样本(也就是统计信息)来做分析,统计信息的量通常来说会远低于原数据,所以分析和生成执行计划的速度会快得多。但是正如我一直在很多文章中说到的一样,没有什么功能是绝对的好或者绝对的坏,统计信息也有缺点,这个缺点就是维护成本,对于大型数据库的统计信息创建和维护(实时更新)会消耗很多资源和时间,另外由于统计信息是数据表/索引的取样结果,所以对于超大型的表来说,准确程度不可能太高。

统计信息的样子:

下面我们来看看上一节创建的演示库中统计信息的样子,先用以下脚本创建数据库环境:

use StatisticsTest; go -- Create a test table if (object_id('T0', 'U') is not null) drop table T0; go create table T0(c1 int not null, c2 nchar(200) not null default '#') go -- Insert 100000 rows. All rows contain the value 1000 for column c1 insert T0(c1) select 1000 from Numbers where n <= 100000 go -- Now insert only one row with value 2000 insert T0(c1) values(2000) go --create a nonclustered index on column c1 create nonclustered index ix_T0_1 on T0(c1)

首先看看图形化的统计信息,我们可以在SSMS的这个地方找到统计信息:


[原]全废话SQL Server统计信息(2)――统计信息基础

在环境创建完之后,可以发现统计信息这个文件夹下面是没有东西的,因为表没有“被使用”,所以优化器不会对这个表创建任何统计信息。但是当第一次使用或者创建索引(实际上也是对数据进行使用)时,就会创建统计信息,我们可以尝试两个操作,第一个是执行一个简单的SELECT语句,优化器会对上面用到的列创建统计信息:


[原]全废话SQL Server统计信息(2)――统计信息基础

需要注意要带上WHERE条件,其中竖框部分的1代表表创建时的第一列也就是x,而_WA_Sys代表由SQL Server自动创建的统计信息,WA传说是SQL Server开发组所在地华盛顿(Washington)的缩写。

下面再来创建一个索引,即前面脚本中注释掉的那段:


[原]全废话SQL Server统计信息(2)――统计信息基础

可以看到又多了一个统计信息,并且这个统计信息是和索引名一样,这个可以说是SQL Server自己创建的(因为你没有显式编写命令单独创建统计信息),也可以说是用户操作导致的。为了和前面_WA这个做区别,我们通常把它定义为非SQL Server自动创建的统计信息。

SQL Server统计信息元数据

下面我们来看看如何查询统计信息,统计信息是独立于实体表/索引的实际存储的信息,我们可以从一些元数据中获取它们。SQL Server 2005开始引入了目录视图、动态管理对象(DMO)等替代2000时代的系统表,减少对系统表的误操作所带来的系统故障,同时这些视图也添加了很多详细信息供后续使用。关于统计信息,我们首先用到的目录视图是:sys.stats,注意这部分的元数据存储在对应的数据库中,所以也需要切换到对应的数据库下执行:

use StatisticsTest GO SELECT * FROM sys.stats WHERE object_id = object_id('dbo.T1')
[原]全废话SQL Server统计信息(2)――统计信息基础

可以看到每一个统计信息都单独存在一行中,可以使用DBCC SHOW_STATISTICS命令来对某个统计信息进行详细展示:


[原]全废话SQL Server统计信息(2)――统计信息基础

如果要查询的统计信息不存在(或者拼错),会得到以下错误:

消息 2767,级别16,状态 1,第 8 行

无法在系统目录中找到统计信息 'a'。

DBCC 执行完毕。如果 DBCC 输出了错误信息,请与系统管理员联系。


[原]全废话SQL Server统计信息(2)――统计信息基础

在这里是因为我们从创建开始就没有使用过a这个列,所以只要我们执行一个使用到它的语句,然后就可以查询:


[原]全废话SQL Server统计信息(2)――统计信息基础

回到DBCC SHOW_STATISTICS命令得到的结果,前面提到了统计信息主要有三个部分,从上图看到也确实有三部分,这三部分分别叫做头信息、密度信息和直方图。


[原]全废话SQL Server统计信息(2)――统计信息基础
头信息:
[原]全废话SQL Server统计信息(2)――统计信息基础

下面来看看每个列的简要说明:

Name :_WA_Sys_00000002_108B795B,这是统计信息的名字,所以自动创建的统计信息都以_WA_Sys开头,跟着是一个值,标识为统计信息是基于哪一列创建的,注意自动创建的统计信息只会在单列上,对于多列组合的统计信息必须手动创建,同时这个列顺序是表创建时候的顺序,可以通过sys.columns目录视图查看,接下来是一个十六进制的值,代表表的object_id,可以用windows自带的计算器反计算。然后使用Object_Name()函数得出表名:
[原]全废话SQL Server统计信息(2)――统计信息基础
[原]全废话SQL Server统计信息(2)――统计信息基础
Updated :09 17 2016 5:13PM 这个值是统计信息创建或最后一次更新的时间。 Rows :100000,表示统计信息创建或最后一次更新的时间。 Rows Sampled :100000,表示统计信息创建或最近一次更新时的取样行数。 Steps :109,直方图的步数,接下来会介绍。 Density :0.03002139,密度值,这个值在新版(最晚在SQL 2008开始)SQL Server中仅用于向后兼容,对优化器没有用处。 Average key length :3.62263,统计信息所针对的列的平均字节数。这个值可以通过这样计算出来,虽然没有多大研究价值:
[原]全废话SQL Server统计信息(2)――统计信息基础

即把列中每行的字节数(注意datalength函数返回字节数,len()函数返回字符数)加起来再除以总行数即可。

String Index :YES,这个值表示统计信息是否包含字符串信息,只有YES或者NO可选。这个值可以对LIKE条件提供预估支持,并且字符串统计信息只对第一列创建且必须为字符串类型(单列、多列统计信息都一样),由于这里的统计信息是建在A列,而这列是字符串类型,所以这里的值为YES。 FilterExpression和UnfilteredRows :这两个值只在统计信息创建在过滤索引(filter index)上才出现非null的值,后面会介绍。 密度信息:
[原]全废话SQL Server统计信息(2)――统计信息基础

本例中比较简单,是单列统计信息,所以密度信息这部分比较少,后面会演示多列统计信息,从名字来看,就三列:

All density :0.0003333333,它是对于这列(多列的先不管),1/唯一值的个数。
[原]全废话SQL Server统计信息(2)――统计信息基础
Average Length :3.62263,表示唯一值的平均长度。 Columns :a ,明显代表这个密度是包含哪些列,不罗嗦。

那么这部分内容有啥用呢?大部分情况下,这部分的信息可以对语句中的GROUP BY 和ON条件中的未知值(比如本地变量)提供信息给优化器。再次看看这个表的统计信息情况:

DBCC SHOW_STATISTICS('Sales.SalesOrderDetail', IX_SalesOrderDetail_ProductID)
[原]全废话SQL Server统计信息(2)――统计信息基础

这里表示了这个索引包含了3列,每种组合情况下的密度及平均长度信息。下面来看看这个语句:

USE AdventureWorks2014 GO SELECT ProductID FROM Sales.SalesOrderDetail GROUP BY ProductID

优化器在编译这个语句时,由于ProductID列上有统计信息,不需要遍历整表,直接从密度信息中就可以获取唯一值(Group By本质就是去重)的预估行数。


[原]全废话SQL Server统计信息(2)――统计信息基础

这个值,根据定义,是1/对应的密度,由于只计算ProductID,所以密度就是


[原]全废话SQL Server统计信息(2)――统计信息基础

结果大家可以自行算一下,是265.99996。大家也可以自己算一下GROUP BY ProductID,SalesOrderID及SalesOrderDetailID,也就是上面的另外两行。

那么在使用本地变量的情况下会如何 ?

USE AdventureWorks2014 GO DECLARE @ProductID INT SET @ProductID = 921 SELECT ProductID FROM Sales.SalesOrderDetail WHERE ProductID = @ProductID
[原]全废话SQL Server统计信息(2)――统计信息基础

由于本地变量在统计信息的行为上有点特殊,所以这里要特意拿来说一下,优化器在最终实际运行之前没办法知道提交的SQL语句中参数会是什么值,但是它又必须产生一个预估执行计划,同时接下来会解释,此时也没办法使用直方图,所以优化器只能借助密度信息来获取预估行数。此时的预估行数由表总数乘以密度信息得出。也就是:0.003759399 * 121317≈456.079。

另外一个情况下,WHERE条件不是使用“等于”符号,而是“不等于”,


[原]全废话SQL Server统计信息(2)――统计信息基础
这个时候,参数的具体值已经没所谓了,大家可以试一下随便填一个值。此时优化器连密度信息都不能用了,但是又必须给点东西,怎么办?使用一个标准假设(表总数的30%作为选择度),也就是说在这种不等

Viewing all articles
Browse latest Browse all 3160

Trending Articles