zoukankan      html  css  js  c++  java
  • SQL Server索引进阶第十四篇:索引统计

      索引设计是数据库设计中比较重要的一个环节,对数据库的性能其中至关重要的作用,但是索引的设计却又不是那么容易的事情,性能也不是那么轻易就获取到的,很多的技术人员因为不恰当的创建索引,最后使得其效果适得其反,可以说“成也索引,败也索引”
        本系列文章来自Stairway to SQL Server Indexes,然后经过我们团队的理解和整理发布在agilesharp,希望对广大的技术朋友在如何使用索引上有所帮助。

    系列文章索目录:

    SQL Server索引进阶第一篇:索引介绍
    SQL Server索引进阶第二篇:深入非聚集索引
    SQL Server索引进阶第三篇:聚集索引
    SQL Server索引进阶第四篇:页和区

    SQL Server索引进阶第五篇:索引包含列
    SQL Server索引进阶第六篇:书签
    SQL Server索引进阶第七篇:过滤的索引

    SQL Server索引进阶第八篇:唯一索引
    SQL Server索引进阶第九篇:解读执行计划
    SQL Server索引进阶第十篇:索引的内部结构
    SQL Server索引进阶第十一篇:索引碎片分析与解决(上)
    SQL Server索引进阶第十一篇:索引碎片分析与解决(中)-碎片发生原理深度剖析
    SQL Server索引进阶第十二篇:索引的创建,修改和删除
    SQL Server索引进阶第十三篇:Insert,Update,Delete语句
    SQL Server索引进阶第十四篇:索引统计
    SQL Server索引进阶第十五篇:索引的最佳实践



        在第十篇文章中我们详述了为什么索引需要叶子节点和非叶子节点,我原文是”然而,SQL Server并不知道什么是按字母表排序”,换句话说,SQL Server并不知道“Meyer, Helen”这个条目大概在索引的中间位置。

        但是SQL Server需要知道有关索引中数据分布的情况;正如我们所知,查询的选择性是决定是否使用特定索引的关键。SQL Server存储索引键中的部分值,这部分值就是所谓的索引统计信息,也可以直接叫做统计信息,统计信息是本篇文章要涵盖的主题。
        实际上,统计信息不仅仅是索引列值的采样,统计信息也会采样非索引列,这部分信息的结构和采样索引列的结构是一致的,但由于本系列文章是关于索引的,所以这里仅对统计列进行讨论。
        索引统计有点像汽车中的引擎。当然你知道引擎的原理那是再好不过的,但如果你不懂引擎的原理,那依然需要定期维护引擎。本篇文章讲述索引统计的原理和维护这两方面。

    索引统计的结构
        索引统计信息分为三部分:索引头信息,密度信息和数据分布信息,如果想查看这三部分信息,则运行:

    1. DBCC SHOW_STATISTICS (tablename, indexname)
    复制代码

    而如果要分别查看这三个部分中某一个部分的信息,则运行:

    1. DBCC SHOW_STATISTICS (tablename, indexname) WITH STAT_HEADER
    复制代码

    或是:

    1. DBCC SHOW_STATISTICS (tablename, indexname) WITH DENSITY_VECTOR
    复制代码

    或是:

    1. DBCC SHOW_STATISTICS (tablename, indexname) WITH HISTOGRAM
    复制代码

    在我们开始查看统计信息之前,首先我们来建立一个表和索引来作为示例进行演示。我们的示例表由3列和两个索引组成。两个索引分别是表的前两列和第三列。代码1所示。

    1. USE AdventureWorks;
    2. GO
    3. IF EXISTS (SELECT *
    4. FROM sys.objects
    5. WHERE name = 'HistogramTest' AND type = 'U')
    6. BEGIN
    7. DROP TABLE dbo.HistogramTest
    8. END
    9. GO
    10. CREATE TABLE dbo.HistogramTest
    11. (
    12. Col1 int not null
    13. , Col2 int not null
    14. , Col3 int not null
    15. )
    16. GO
    17. CREATE INDEX IX_HistogramTest
    18. ON dbo.HistogramTest
    19. ( Col1, Col2 )
    20. CREATE INDEX IX_SingleValue
    21. ON dbo.HistogramTest
    22. ( Col3 )
    23. GO
    复制代码

    代码1.有两个索引的示例表

        表建好之后,让我们来插入一些测试数据,如代码2所示。

    1. SET NOCOUNT ON;
    2. GO
    3. DECLARE @maxLeftColumnValue int = 100;
    4. DECLARE @leftColumnValue int = 0;
    5. DECLARE @middleColumnValue int = 0;
    6. DECLARE @rightColumnValue int = 0;
    7. WHILE @leftColumnValue < @maxLeftColumnValue
    8. BEGIN
    9. SET @leftColumnValue += 1
    10. SET @middleColumnValue = 0
    11. WHILE @middleColumnValue < @leftColumnValue
    12. BEGIN
    13. SET @middleColumnValue += 1
    14. SET @rightColumnValue += 1
    15. INSERT dbo.HistogramTest VALUES ( @leftColumnValue
    16. , @middleColumnValue
    17. , @rightColumnValue )
    18. END
    19. END
    20. GO
    复制代码

    代码2.插入一些测试数据

        测试数据特点如下:



    • Col1中的数据相同值的行数和其值相同,比如值为1的有1行,值为2的有2行,依此类推
    • 但对于行来说,Col1和Col2作为组合列值是唯一的。所以建立在其上的IX_HistogramTest索引可以看所是唯一索引
    • Col3中的值是唯一的,从1开始不断递增,因此IX_SingleValue也可以看作是唯一索引

        为了演示的准确性,上面的顺序是先创建表,再插入测试数据,然后再创建索引,这样就能保证索引的统计信息是最新的。
        完成上面的步骤之后,运行代码3中所示的查询。可以看到结果和我们上面所说的数据特点一致。
        因为这部分数据是按照Col1/Col2排序的,因此可以看所是IX_Histogram索引的部分数据。

    1. SELECT TOP 20 Col1, Col2, Col3
    2. FROM dbo.HistogramTest
    3. ORDER BY Col1, Col2;
    复制代码

    代码3.查询语句


    图1.查询结果

    密度
        另一个有关索引的术语就是密度。密度是衡量索引中数据重复程度的一个术语,和我们之前文章所谓的选择性关系紧密。比如说密度是0.01代表着索引某列中有1/0.01=100个不相同的值。换句话说,平均每一个值出现的概率是1%。密度可以衡量单一列的密度,也可以用于衡量组合列的密度,比如前面例子中的Col1-Col2组合列。
        如果图1的查询结果是整个表,则Col1的密度会是1/6=0.1667,而col3的密度是1/20=0.05
        密度越小,则查询中比较谓词所能匹配的数据越少。则选择性越高。比如Col3的选择性就比Col1高。

    统计信息头
    执行下面语句:

    1. DBCC SHOW_STATISTICS('dbo.HistogramTest', 'IX_HistogramTest')
    2. WITH STAT_HEADER
    复制代码

    我们可以看到如图2所示的统计头信息:


    图2.统计信息头

    头部信息涵盖了如下内容:
    Name:索引信息名称。
    Updated:统计信息的更新时间。
    Rows:索引中包含的条目数。
    Steps:步长,代表了数据分布信息的份数。
    Density:这个值在SQL Server 2008之后被淘汰。
    Average Key Length:平均键的长度.
    String Index:用于Like匹配时估计行数
    Filter Expression:过滤索引表达式
    Unfiltered Rows:索引所在表的条目数,如果是过滤索引,这个值通常更大。

    统计信息密度
    执行下面语句:

    1. DBCC SHOW_STATISTICS('dbo.HistogramTest', 'IX_HistogramTest')
    2. WITH DENSITY_VECTOR
    复制代码

    我们可以看到如图3所示的结果:


    图3.密度信息

    通过图3的密度信息可以看出:
    Col1平均的键大小是4字节,包含1/0.01=100个不同的值。
    Col1-Col2组合键值的平均大小是8字节,包含1/0.0001980198=5050个不同的值,因为这两列组合起来是唯一的,所以这个5050等于表中的行数。

    统计数据分布直方图
    执行下面代码后:

    1. DBCC SHOW_STATISTICS('dbo.HistogramTest', 'IX_HistogramTest')
    2. WITH HISTOGRAM
    复制代码

    我们可以看到如图4所示的信息,我们截取59个步长中的25个,显示在下面:


    图4.统计分布直方图的前25个步长信息

    数据分布直方图更像是一个5列的表。每一行所存储的信息都可以称为步长。所有的步长信息都取自索引键最左边列的信息。对于我们之前创建的IX_HistogramTest索引,只有COL1的值被采样,其它列在生成数据分布信息的过程中直接被无视。
    每一个步长都是统计分布的一个范围,换句话说是一部分连续索引条目的集合。无论表有多大,数据分布直方图不会超过200个步长。每个步长都包含不同大小的索引条目,比如说当前步长包含30个索引条目,下一个步长包含了46个索引条目。
    为了更好的理解数据分布直方图,我们定义如下4个术语,并在图5中进行详细阐述。



    • 最索引的最左列是索引键中出现在第一个位置的列,比如我们的例子中,Col1就是左列
    • 步长集就是索引中连续条目的集合.图4中这5列值都是根据索引键中的最左列决定的
    • 步长中的Range_High_key是在步长中最左列的最大值
    • 步长中的更小子集是那些小于步长中最左列最大值的集合

    比如说,我们通过图4中部分统计分布图来看上面的概念,如图5所示。


    图5.索引中的步长集

        因为上面的例子最左列只有100个不同的值,所以每一个步长仅仅包含2个值或是更少,一会在本文的后续部分,我们用一个更大的例子来展示包含多个值的步长。
    图4中的5列的统计分布直方图中包含的值计算方法如下:

    RANGE_HI_KEY:
        步长集合中的最左列的最大值。RANGE_HI_KEY是步长的边界,换句话说,不同的步长不和能有相同的RANGE_HI_KEY。
        对于第一个步长来说,RANGE_HI_KEY是最左列最小值,因此第一个步长没有低子集.对于最后一个步长来说,RANGE_HI_KEY是索引最左列的最大值。
        步长按照RANGE_HI_KEY顺序维护和显示。
        我们的例子中,第一个RANGE_HI_KEY的值是1,也就是Col1的最小值。最后一个RANGE_HI_KEY的值是100,也就是Col1的最大值。59个步长中没有一个相同的RANGE_HI_KEY值。
        因为RANGE_HI_KEY是区分步长的一句,因此这个值也可以唯一确定步长,因此步长的RANGE_HI_KEY=20的话,无论这个步长是该索引的第几个步长,我们都可以其为步长20。

    EQ_ROWS:

        最左列值等于RANGE_HI_KEY的行数,也就是步长的大子集(Upper subset)的个数。
        在我们的例子中,步长20中有20个等于该步长RANGE_HI_KEY值的条目。这个值和测试数据生成规则推算出来的值一样。

    RANGE_ROWS:
        步长小子集(lower subset)中条目的个数
        我们的例子中,步长20代表着最左列的值从18到20(18<步长<=20),其中值为19的就是小子集(lower subset),值等于19的条目数有19个。

    DISTINCT_RANGE_ROWS:
        步长小子集(lower subset)中唯一值的个数。
        我们的例子中,步长20代表着最左列的值从18到20(18<步长<=20),其中值为19的就是小子集(lower subset),这个值只有1个。

    AVG_RANGE_ROWS
         最左值的最小集合中每个唯一值条目个数的平均数,可以使用公式RANGE_ROWS / DISTINCT_RANGE_ROWS进行计算,如果RANGE_ROWS等于0,那么这个值就是1。因为如果RANGE_ROWS等于0的话,这列值毫无意义。

    一个大一点的例子
        前面讨论的例子数据量比较小。这也使得我们可以更简单的知道上面所说的各类值的意义。但是对于SQL Server来说,大表比小表更需要统计信息来做正确的查询计划选择。因此,我们将上面的数据量扩大100倍。
        简而言之,我们将生成测试数据的变量值进行修改。
    由:

    1. DECLARE @maxLeftColumnValue int = 100;
    复制代码

    变为:

    1. DECLARE @maxLeftColumnValue int = 1000;
    复制代码

    我们重新创建表,插入测试数据,并重建了索引。这时表中的数据就编程了505000。
    再次运行:

    1. SHOW_STATISTICS('dbo.HistogramTest', 'IX_HistogramTest')
    复制代码

    可以看到如图6锁示的结果,共有199个步长。


    图6.大量数据例子的索引统计

    图6中的第二个步长,最左列的值为46,意味着大子集(Upper subset)包含46行(每行的最左列的值都是46),这点可以根据我们前面数据生成算法推出。在小子集(Lower subset)包含1034个条目(2+3+4+44+45),这些数据共有44个不同值(从2到45)。则平均每一个不同最左列的值有23.5行。
    所以,当SQL Server解析下面这句时:
    WHERE Col1 = 46
    SQL Server就会知道500500行中有46行满足这个where条件。
    而如果谓语是:
    WHERE Col1 = 45
    因为这里行不在RANGE_HI_KEY中,所以SQL Server就采用平均值,也就是23.5行。
    即使遇到如下谓语:
    WHERE Col1 BETWEEN 2 AND 24
    SQL Server也能知道将会有517行(大概上面的小子集(Lower subset)中一半的行)。

    唯一索引和统计信息
        我们前面所创建的两个索引IX_HistogramTest和IX_SingleValue都符合建立唯一索引的条件,在实际生产环境中它们也应该是被设置成唯一索引。虽然SQL Server可以从统计信息中得知这两个索引中键值是唯一的,但是这是无法保证的。所以仅仅靠索引统计信息是无法保证SQL Server在生成执行计划的过程中确定索引中键值的唯一性。在本系列的第八篇关于唯一索引的文章中,我们已经知道SQL Server是如何利用唯一索引来生成更好的执行计划的,但仅靠索引统计信息不行。
        同样,如果只存在唯一索引而没有统计信息,SQL Server则仅仅知道值是唯一的而不知道值到底是什么。因此为了知道索引中值的范围,SQL Server首先要查看统计信息,比如我们之前创建的IX_SingleValues索引,SQL Server首先要知道下面的信息:

    1. DBCC SHOW_STATISTICS('dbo.HistogramTest', 'IX_SingleValue')
    复制代码

    信息的结果如图7所示。


    图7.SQL Server对于单列唯一索引的统计信息

    SQL Server需要在查询过程中得知如下两部分信息:



    • 索引包含500500个条目
    • 这些条目由500500(1+500548+1)个不同值组成,从1到500500

    得知了这些信息之后,遇到WHERE子句中不管是=,<,>或是between,SQL Server都可以根据上面的信息快速估计返回的行数。
    字符串统计信息
        字符串类型(包括Char,varchar,nchar,nvarchar和ntext类型)和数字类型不同之处在于字符串类型是可以分割的。比如substring函数或是like操作需要SQL Server去匹配字符串的子集。因此,SQL Server需要一些统计信息来得知特定字符串子集的值出现的概率。这就是所谓的字符串统计信息。这个统计信息是在SQL Server 2008之后才有的。
        如果统计头中StringIndex列的值是Yes,则SQL Server为最左列的值生成字符串统计信息,如果最左列的字符串统计信息超过80字节,则只有开始40字节和结束40字节被用来生成统计信息。
        执行DBCC SHOW_STATISTICS是无法找到关于字符串统计的细节的,SQL SERVER也没有其它方式可以展现字符串统计信息。

    索引统计信息的维护
        统计信息的用处是巨大的。但如果统计信息过时了,也就是反映不出表中的数据分布了则就没那么拥有了。统计信息过时的原因是SQL Server并不会每次在表中插入和删除信息时就同时更新统计信息。如果更确切的说,SQL Server不会更新统计信息,而是重新生成统计信息。但我们也不希望SQL Server重新生成统计信息的次数超过必须的次数。
        因此我们需要知道什么时候需要重新生成统计信息。

    何时更新统计信息
        所幸,SQL Server允许你控制何时重新生成统计信息。
        数据库有一个选项叫做AUTO_UPDATE_STATISTIC,这个选项默认是ON。可以通过ALTER DATABASE语句进行设置。当这个选项设置为ON的时候,表中一定量的数据改变就会引起SQL Server重新生成执行计划,这个“一定量”是由SQL Server控制的,也就是不能通过选项进行控制。
        每当创建或修改索引时,都可以通过STATISTICS_NORECOMPUTE选项来控制索引的统计信息是否会被自动更新。

        如果你想手动更新统计信息,使用下面语句:
    1. UPDATE STATISTICS SchemaName.TableName
    复制代码
    上面的语句会更新与表相关的所有统计信息,而如果你指向更新特定索引的话,请使用:
    1. UPDATE STATISTICS SchemaName.TableName IndexName
    复制代码
    除了这种方法之外,sp_updatestats存储过程也可以用来更新统计信息。

    通常来说,AUTO_UPDATE_STATISTICS保持默认是不错的选择。不仅可以省去麻烦,还能让SQL Server来管理索引统计信息的生成。
    但有些时候你还是想手动更新统计信息,比如下面情况:



    • 你们公司有定期的维护窗口时间,这个时间内你想手动更新统计信息
    • 存在一个很大的表,自动更新统计信息需要达到的阈值太大
    • 索引中含有多个唯一键值,这需要很大的样本数据才能生成有意义的统计信息。默认的样本百分比打不到这个标准
    • 统计信息滞后与表中数据的改变

    为了演示上面最后一种情况,并且更好的理解为什么有些时候不用自动更新统计信息。请看代码4中创建的表,这个表中存储借出图书的信息。

    1. CREATE TABLE dbo.Loan
    2. (
    3. MemberNo int not null
    4. , ISBN SysName not null
    5. , DateOut Date not null
    6. , DateDue Date not null
    7. )
    8. GO
    复制代码

    代码4.Load表

        上面4列分别代表:



    • 借书人的会员ID
    • ISBN号
    • 借出日期
    • 还书日期


        因为所有借出去的书需要在14天内还回来,所以14天内数据是不会被删除的。这里书借出去就插入一条记录,书被换回来则把借书记录删除。在两周内,通常ISBN号的分布几乎不会有太大变化。但还书日期值的分布却有很大变化。
        ISBN号的变化不大是因为很少有哪本书这周不流行,但下周突然流行。因此,ISBN号的数据分布本月和上月一般没有什么太大变化。单个ISBN号肯定有变化,但总体的数据样本不会有太大变化。
        但还书日期列可就不同了,因为这个日期的范围是当前日期+14天,所以数据分布随时会变,并且这列的值不会和过去的值重叠。因此DueDate列的索引需要比ISBN列的索引更新的更加频繁。

    最小化更新统计信息带来的影响
        SQL Server还可以允许用户控制更新统计信息时读取索引的数量或百分比。



    • 当索引创建完成后,必须读取所有的索引条目,因此统计信息需要读取所有的索引条目才能生成样本
    • 当更新索引时,如果还需要采样索引的所有条目就有点小题大做了。这会消耗大量的IO,一般这时SQL SERVER只会采样索引的一部分。
    • 默认情况下SQL Server控制采样的大小,但是你也可以设置这个大小。当更新索引时,你可以指定SAMPLE N PERCENT或是SAMPLE n ROWS亦或是FULLSCAN选项。当你指定了SAMPLE N PERCENT或是SAMPPLE N ROWS,这个效果会立刻在接下来的更新操作和以后的自动更新操作中生效。

        为了阐述不是默认全表采样的部分采样过程,我们重新创建了前面的那个500500行的大表,但这次,我们在创建完之后,分别执行:

    1. UPDATE STATISTICS dbo.HistogramTest;
    2. DBCC SHOW_STATISTICS('dbo.HistogramTest', 'IX_HistogramTest');
    复制代码

    我们看到如图8所示的结果。


    图8.采样的统计信息

        虽然图6和图8中的信息貌似相同,但细节上还是不同的。RANGE_ROWS中的实际实际行数被FLOAT类型的估计行数所替代。

    总结
        索引统计帮助SQL Sever优化查询。
        索引创建和重建时统计信息会自动生成。
        索引统计信息分为三部分,可以通过DBCC指定进行查看。
        索引统计可以过时,过时的统计信息会导致不精确的查询计划。
        默认情况下,索引统计信息会自动更新,你也可以不允许自动更新,也可以手动更新统计信息。
        通常来说更新统计信息并不需要读取索引的所有叶子节点,而是采样部分索引。你可以通过参数来控制采样数据的百分比。

  • 相关阅读:
    贪心法之最优装载问题
    判断回文
    P1217 [USACO1.5]回文质数 Prime Palindromes
    李白打酒
    P1036 选数
    P1028 数的计算
    P1316 丢瓶盖
    P1181 数列分段Section I
    P1182 数列分段`Section II`
    P1216 [IOI1994][USACO1.5]数字三角形 Number Triangles
  • 原文地址:https://www.cnblogs.com/lteal/p/2799343.html
Copyright © 2011-2022 走看看