SQLSERVER是怎麽通过索引和统计信息来找到目标数据的(第三篇)
最近真的没有什么精力写文章,天天加班,为了完成这个系列,硬着头皮上了
再看这篇文章之前请大家先看我之前写的第一篇和第二篇
第一篇:SQLSERVER是怎麽通过索引和统计信息来找到目标数据的(第一篇)
第二篇:SQLSERVER是怎麽通过索引和统计信息来找到目标数据的(第二篇)
1、统计信息的含义与作用
为了以尽可能快的速度完成语句,光有索引是不够的。对于同一句话,SQLSERVER有很多种方法来完成他。
有些方法适合于数据量比较小的时候,有些方法适合于数据量比较大的时候。同一种方法,在数据量不同的时候,
复杂度会有非常大的差别。索引只能帮助SQLSERVER找到符合条件的记录。SQLSERVER还需要知道每一种操作
所要处理的数据量有多少,从而估算出复杂度,选取一个代价最小的执行计划。说得通俗一点,SQLSERVER要能够
知道数据是“长得什么样”的才能用最快方法完成指令
SQLSERVER不像人,光看看数据就能够大概心理有数。那么怎麽能让SQL知道数据的分布信息呢?
在数据库管理系统里有个常用的技术,就是数据“统计信息(statistics)”
SQLSERVER就是通过他了解数据的分布情况的
下面可以先来看前两篇文章的两张范例表在SalesOrderID这个字段上的统计信息,以便对这个概念有点直观认识
dbo.SalesOrderHeader_test保存的是每张订单的概要信息,一张订单只会有一条记录
所以SalesOrderID是不会重复的。现在这张表里,应该有31474条记录。SalesOrderID是一个int型的字段,
所以字段长度是4。
运行
1 DBCC SHOW_STATISTICS(tablename,INDEX OR STATISTICS name)
2
3 DBCC SHOW_STATISTICS([SalesOrderHeader_test],SalesOrderHeader_test_CL)
统计信息内容分3部分
1、统计信息头信息
列名 说明
name 统计信息的名称,这里就是索引的名字
updated 上一次更新统计信息的日期和时间。这里是12 18 2012 1:16AM 这个时间非常重要,根据他能够判断统计信息是什么时候更新的 是不是在数据量发生变化之后,是不是存在统计信息不能反映当前 数据分布特点的问题
rows 表中的行数。这里是31465行,不能完全完全正确地反映了当前表里数据量(因为统计信息没有及时更新)
rows sampled 统计信息的抽样行数这里也是31465,说明上次SQL更新统计信息 的时候,对整个表里所有记录的SalesOrderID字段,都扫描了一遍 ,这样做出来的统计信息一般都是很精确的
steps 在统计信息的第三部分,会把数据分成几组,这里是3组
density 第一个列前缀的选择性(不包括EQ_ROWS)
average key length 所有列的平均长度,因为SalesOrderHeader_test_CL索引只有一列数据类型是int,
所以长度是4(单位是字节),如果索引有多个列,每个列的数据类型都不一样,
比如再有一个列colc char(10) 那么平均长度是(10+4)/2=7
string index 如果为“是”,则统计信息中包含字符串摘要索引,以支持为LIKE条件 估算结果集大小。仅适用于char,varchar,nchar和nvarchar,varchar(max) nvarchar(max),text,ntext 数据类型的前导列。这里是int,所以这个值是“NO”
2、数据字段的选择性 列名 说明
all density 反映索引列的选择性(selectivity) "选择性"反映数据集里重复的数据量是多少,或者反过来说,值唯一的数据量 有多少。如果一个字段的数据很少有重复,那么他的可选择性就比较高。比如 身份证号,是不可重复的。哪怕对整个中国的身份记录做查询,代入一个身份证号码 最多只会有一条记录返回,在这样的字段上的过滤条件,能够有效地过滤掉大量数据 返回的结果集会比较小 举个相反的例子:性别。所有人只有两种,非男即女。这个字段上的重复性就很高 选择性就很低。一个过滤条件,最多只能过滤掉一半的记录 SQL通过计算“选择性”,使得自己能够预测一个过滤条件做完后,大概能有多少记录 返回 Density的定义是: density = 1/cardinality of index keys 如果这个值小于0.1,一般讲这个索引的选择性比较高,如果大于0.1,他的选择性 就不高了。这里[SalesOrderHeader_test]有31474条没有重复的记录 1/31474 = 3.177e-5 这个字段的选择性是不错的
average length 索引列的平均长度,这里还是4
columns 索引列的名称,这里是字段名 SalesOrderID
从这一部分的信息,可以推断出统计信息所关心的字段的长度,以及他有多少条唯一值。但是这些信息对SQLSERVER预测结果集复杂度还不够。
比如我现在要查一个SalesOrderID=60000的订单,还是不知道会有多少记录返回。这里需要第三部分的信息
3、直方图(histogram) 列名 说明 range_hi_key 直方图里每一组(step)数据的最大值 订单号的最小号码在表格里是43659,这里SQL选择他作为第一个step 的最大值,3组数据分别是 ~43659 43660~75131 75132~75132
range_rows 直方图里每组数据区间行数,上限值除外 第一组只有一个数:43659 第三组也只有一个数:75132,其他数据都在第二组里,区间里有31471个数
EQ_ROWS 表中值与直方图每组数据上限值相等的行数目 这里都是1
distinct_range_rows 直方图里每组数据区间非重复值的数目,上限值除外由于这个字段没有重复值,所以这里 就等于range_rows的值
avg_range_rows 直方图里每组数据区间内重复值的平均数目,上限值除外。计算公式 (range_rows/distinct_range_rows for distinct_range_rows>0) 这里distinct_range_rows的值就等于range_rows的值,所以avg_range_rows等于1
有这麽一个直方图,就能够很好地知道表格里的数据分布了。在SalesOrderID这个字段里,最小值是43659,
最大值是75132,在这个区间里有31473个值,而且没有重复值,所以可以推算出表里的值就是从43659开始到75132结束的每个int值。
SQL没有必要存储很多step的信息,只要这3个step,就能够完全表达数据分布
这里要说明两点的是:
(1)如果一个统计信息是为一组字段建立的,例如一个复合索引建立在两个以上的字段上,SQLSERVER维护所有字段的选择性信息,
但是只会维护第一个字段的直方图。因为第一个字段的行数就是整张表的行数,就算那个字段在某条记录里为null,SQLSERVER也会做统计
(2)当表格比较大的时候,SQLSERVER在更新统计信息的时候为了降低消耗,只会取表格的一部分数据做抽样(rows sample),
这时候统计信息里面的数据都是根据这些抽样数据估算出来的值可能和真实值会有些差异
统计信息越细致,当然会越精确,但是维护统计信息要付出的额外开销也就越大。有可能提高统计信息精确度所带来的执行性能的提升
还抵消不了维护统计信息成本的增加。 SQLSERVER做这样的设计,不是因为其能力有限,而是为了谋求一个对大多数情况都合适的平衡
-------------------------------------------统计信息的维护和更新---------------------------------
当SQLSERVER需要去估算某个操作的复杂度时,他必定要试图去寻找相应的统计信息做支持。
DBA无法预估SQLSERVER会运行什么样的操作,所以也无法预估SQLSERVER可能需要什么样的统计信息
如果靠人力来建立和维护统计信息,那将是一个非常复杂的工程。好在SQLSERVER不是这样设计的
在绝大多数情况下,SQLSERVER自己会很好地维护和更新统计信息,用户基本没有感觉,DBA也没有额外的负担。
这主要是因为在SQLSERVER 数据库属性里,有两个默认打开的设置
auto create statistics 自动创建统计信息
auto update statistics自动更新统计信息
他们能够让SQLSERVER在需要的时候自动建立要用到的统计信息,也能在发现统计信息过时的时候,自动去更新他
SQLSERVER会在什么情形下创建统计信息呢?
主要有3种情况
(1)在索引创建时,SQLSERVER会自动在索引所在的列上创建统计信息,所以从某种角度讲,索引的作用是双重的,
他自己能够帮助SQLSERVER快速找到数据,而他上面的统计信息,也能够告诉SQLSERVER数据的分布情况
补充一下:索引重建的时候也会更新表的统计信息,所以有时候查询变慢的时候重建一下索引查询变快了统计信息的更新也是原因之一
(2)DBA也可以通过之类的语句手动创建他认为需要的统计信息 CREATE STATISTICS
如果打开了auto create statistics自动创建统计信息,一般来讲很少需要手动创建
(3)当SQSERVERL想要使用某些列上的统计信息,发现没有的时候,“auto create statistics 自动创建统计信息”
会让SQLSERVER自动创建统计信息
例如,当语句要在某个(或者几个)字段上做过滤,或者要拿他们和另外一张表做联接(join) SQLSERVER要估算最后从这张表会返回多少记录。
这时候就需要一个统计信息的支持。如果没有,SQLSERVER会自动创建一个
在打开“auto create statistics 自动创建统计信息”的数据库上,一般不需要担心SQLSERVER没有足够的统计信息来选择执行计划。
这一点完全交给SQLSERVER管理就可以了
更新统计信息
SQLSERVER不仅要建立合适的统计信息,还要及时更新他们,使他们能够反映表格里数据的变化数据的插入、删除、修改都可能会引起统计信息的更新。
但是,更新统计信息本身也是一件消耗资源的事情,尤其是对比较大的表格。如果有一点点小的修改SQLSERVR都要去更新统计信息,
可能SQLSERVER就得光忙活这个,来不及做其他事情了。SQLSERVER还是要在统计信息的准确度和资源合理消耗之间做一个平衡。
在SQL2005/SQL2008,触发统计信息自动更新的条件是:
(1)如果统计信息是定义在普通表格上,那么当发生下面变化之一后,统计信息就被认为是过时的了。下次使用到时,会自动触发一个更新动作
分离数据库的时候,也可以手动选择是否更新统计信息
1、表格从没有数据变成有大于等于1条数据
2、对于数据量小于500行的表格,当统计信息的第一个字段数据累计变化量大于500以后
3、对于数据量大于500行的表格,当统计信息的第一个字段数据累计变化量大于 --500+(20%*表格数据总量)以后。所以对于比较大的表,
只有1/5以上的数据发生变化后 --SQL才会去重算统计信息
(2)临时表(temp table)上可以有统计信息。其维护策略基本和普通表一致。 但是表变量(table variable)上不能建立统计信息
这样的维护策略能够保证花费比较小的代价,确保统计信息基本正确
SQL2000和SQL2005在更新统计信息的策略上的区别:
在SQLSERVER2000的时候,如果SQLSERVR在编译一个语句时发现某个表的某个统计信息已经过时,
他会暂停语句的编译,转去更新统计信息,等统计信息更新好以后,用新的信息来做执行计划。这样的方法
当然能够帮助得到一个更准确的执行计划,但是缺点是语句执行要等统计信息更新完毕。这个过程有点费时。
在大部分情况下,语句执行效率对统计信息没有那么敏感。如果用老的统计信息也能做出比较好的执行计划,
这里的等待就白等了
所以在SQLSERVER2005以后,数据库属性多了一个“auto update statistics asynchronously自动异步更新统计信息”
当SQLSERVER发现某个统计信息过时时,他会用老的统计信息继续现在的查询编译,但是会在后台启动一个任务,更新这个统计信息。
这样下一次统计信息被使用到时,就已经是一个更新过的版本。这样做的缺点是,不能保证当前这句查询的执行计划准确性。
凡事有利有弊,DBA可以根据实际情况做选择
写完了,可能篇幅很长,不过没有办法,大部分内容都是首尾呼应,没有前面的铺垫可能看不懂下面的内容
--------------------------------------------------------------------------
2013-8-25 补充:
如果需要更新某张表的统计信息,使用下面的SQL语句
1 USE [pratice] --需要更新统计信息的数据库
2 GO
3
4 UPDATE STATISTICS tableA
5 GO
如果需要更新整个数据库的统计信息,使用下面的SQL语句,不带参数
1 USE [pratice] --需要更新统计信息的数据库
2 GO
3 EXEC [sys].[sp_updatestats] --@resample = '' -- char(8)
4 GO
1 正在更新 [dbo].[testpivot]
2 [_WA_Sys_00000001_0425A276],不需要更新...
3 [_WA_Sys_00000002_0425A276],不需要更新...
4 已更新 0 条索引/统计信息,2 不需要更新。
5
6 正在更新 [dbo].[Users]
7 [IX_UserID],不需要更新...
8 [_WA_Sys_00000002_08EA5793],不需要更新...
9 [_WA_Sys_00000003_08EA5793],不需要更新...
10 [_WA_Sys_00000004_08EA5793],不需要更新...
11 [_WA_Sys_00000005_08EA5793],不需要更新...
12 已更新 0 条索引/统计信息,5 不需要更新。
13
14 正在更新 [dbo].[TABLE1]
15 [INDEX_ID],不需要更新...
16 [INDEX_CATEGORYID],不需要更新...
17 已更新 0 条索引/统计信息,2 不需要更新。
18
19 正在更新 [dbo].[TABLE2]
20 [INDEX_CATEGORYID],不需要更新...
21 已更新 0 条索引/统计信息,1 不需要更新。
22
23 正在更新 [dbo].[Orders]
24 [_WA_Sys_00000005_0EA330E9],不需要更新...
25 已更新 0 条索引/统计信息,1 不需要更新。
26
27 正在更新 [dbo].[Department]
28 [CL_DepartmentID],不需要更新...
29 已更新 0 条索引/统计信息,1 不需要更新。
30
31 正在更新 [dbo].[UserInfo]
32 已更新 0 条索引/统计信息,0 不需要更新。
33
34 正在更新 [dbo].[tb_test]
35 已更新 0 条索引/统计信息,0 不需要更新。
36
37 正在更新 [dbo].[Department9]
38 [NCL_Name_GroupName],不需要更新...
39 已更新 0 条索引/统计信息,1 不需要更新。
40
41 正在更新 [dbo].[bulkinserttest]
42 已更新 0 条索引/统计信息,0 不需要更新。
43
44 正在更新 [dbo].[SystemPara]
45 [_WA_Sys_00000001_173876EA],不需要更新...
46 [_WA_Sys_00000002_173876EA],不需要更新...
47 [_WA_Sys_00000004_173876EA],不需要更新...
48 已更新 0 条索引/统计信息,3 不需要更新。
49
50 正在更新 [dbo].[TB]
51 [_WA_Sys_00000001_178D7CA5],不需要更新...
52 [_WA_Sys_00000002_178D7CA5],不需要更新...
53 [_WA_Sys_00000003_178D7CA5