zoukankan      html  css  js  c++  java
  • 包含列的索引:通往SQL Server索引级别5的阶梯

    原文链接:http://www.sqlservercentral.com/articles/Stairway+Series/72276/

    包含列的索引:通往SQL Server索引级别5的阶梯

    大卫•杜兰特2011/07/13

    该系列

    本文是楼梯系列的一部分:SQL Server索引的阶梯

    索引是数据库设计的基础,并告诉开发人员使用数据库非常了解设计器的意图。不幸的是,当性能问题出现时,索引常常被添加到事后。这里最后是一个简单的系列文章,它应该能让任何数据库专业人员快速“跟上”他们的步伐

    前面的级别引入了集群和非聚集索引,突出了每个方面的以下方面:

    表中的每一行都有一个条目(我们注意到这个规则的例外情况将在以后的级别中被覆盖)。这些条目总是在索引键序列中。

    在聚集索引中,索引项是表的实际行。

    在非聚集索引中,条目与数据行分开;并由索引键列和书签值组成,将索引键列映射到表的实际行。

    前半句是正确的,但不完整。在这个级别中,我们检查了将附加的列包含到非聚集索引的选项,称为包含列。在第6级检查书签操作时,我们会看到SQL Server可能会单方面向索引添加一些列。

    包括列

    非聚集索引中的列,但不是索引键的一部分,被称为包含列。这些列不是键的一部分,因此不影响索引中的条目序列。而且,正如我们将看到的,它们比键列的开销更少。

    在创建非聚集索引时,我们将分别从键列指定包含的列;如清单5.1所示。

    CREATE NONCLUSTERED INDEX FK_ProductID_ ModifiedDate
           ON Sales.SalesOrderDetail (ProductID, ModifiedDate)
           INCLUDE (OrderQty, UnitPrice, LineTotal)

    清单5.1:创建包含列的非聚集索引

    在本例中,ProductID和ModifiedDate是索引键列,OrderQty、UnitPrice和LineTotal是包含的列。

    如果我们没有在上面的SQL语句中指定INCLUDE子句,那么结果的索引应该是这样的:

    ProductID ModifiedDate书签

    Page n:

    707         2004/07/25        =>  
    707         2004/07/26        =>  
    707         2004/07/26        =>  
    707         2004/07/26        =>  
    707         2004/07/27        =>  
    707         2004/07/27        =>  
    707         2004/07/27        =>  
    707         2004/07/28        =>  
    707         2004/07/28        =>  
    707         2004/07/28        =>  
    707         2004/07/28        =>  
    707         2004/07/28        =>  
    707         2004/07/28        =>  

    Page n+1:

    707         2004/07/29        =>  
    707         2004/07/31        =>  
    707         2004/07/31        =>  
    707         2004/07/31        =>  
    708         2001/07/01        =>  
    708         2001/07/01        =>  
    708         2001/07/01        =>  
    708         2001/07/01        =>  
    708         2001/07/01        =>  
    708         2001/07/01        =>  
    708         2001/07/01        =>  
    708         2001/07/01        =>  
    708         2001/07/01        =>  
    708         2001/07/01        =>  

    然而,已经告诉SQL Server包括OrderQty、UnitPrice和LineTotal列,索引看起来是这样的:

    --- --- --- --- --- --- --- --- --- --- --- --

    产品修改日期

    Page n-1:

    707         2004/07/29        1           34.99       34.99       =>  
    707         2004/07/31        1           34.99       34.99       =>  
    707         2004/07/31        3           34.99      104.97       =>  
    707         2004/07/31        1           34.99       34.99       =>  
    708         2001/07/01        5           20.19      100.95       =>  

    Page n:

    708         2001/07/01        1           20.19       20.19       =>  
    708         2001/07/01        1           20.19       20.19       =>  
    708         2001/07/01        2           20.19       40.38       =>  
    708         2001/07/01        1           20.19       20.19       =>  
    708         2001/07/01        2           20.19       40.38       =>  

    708         2001/12/01        7           20.19      141.33       =>  
    708         2001/12/01        1           20.19       20.19       =>  
    708         2002/01/01        1           20.19       20.19       =>  
    708         2002/01/01        1           20.19       20.19       =>  
    708         2002/01/01        1           20.19       20.19       =>  

    Page n+1:

    708         2002/01/01        2           20.19       40.38       =>  
    708         2002/01/01        5           20.19      100.95       => 
     
    708         2002/02/01        1           20.19       20.19       =>  
    708         2002/02/01        1           20.19       20.19       =>  
    708         2002/02/01        2           20.19       40.38       =>  

    检查这个索引的内容,很明显,这些行是由索引键列排序的。例如,在2002年1月1日修改后的产品708(以粗体显示)的5行,在索引中是连续的,就像其他所有ProductID / ModifiedDate组合中的行一样。

    你可能会问“为什么要包含列呢?”为什么不直接向索引键添加OrderQty、UnitPrice和LineTotal ?“在索引中有这些列有几个优点,但索引键没有,比如:

    不属于索引键的列不会影响索引内条目的位置。这反过来降低了在索引中使用它们的开销。例如,如果行中的ProductID或ModifiedDate值被修改,那么该行的条目必须在索引中重新定位。但是,如果在行中的unit定价evalue被修改,那么索引项仍然需要更新,但它不需要移动。

    在索引中定位一个条目所需的工作量更少。

    指数的大小将会稍微小一些。

    索引的数据分布统计数据将更容易维护。

    当我们查看索引的内部结构以及SQL Server维护的一些额外信息以优化查询性能时,这些优势在以后的级别中会更有意义。

    决定一个索引列是否是索引键的一部分,或者仅仅是一个包含的列,并不是您所要做的最重要的索引决定。也就是说,在SELECT列表中经常出现的列,而不是查询的WHERE子句中最优的列在索引的列中。

    成为一种覆盖指数

    在第4级,我们与AdventureWorksdatabase的设计人员达成协议,他们决定让SalesOrderID / SalesOrderDetailID为SalesOrderDetail表的集群索引。针对此表的大多数查询将请求按销售订单号排序或分组的数据。但是,一些查询,可能来自仓库人员,将需要在产品序列中的信息。这些查询将从清单5.1中显示的索引中获益。

    为了说明在该索引中包含包含列的潜在好处,我们将查看针对SalesOrderDetailtable的两个查询,每个查询将执行三次,如下:

    运行1:没有非聚集索引

    运行2:使用包含不包含列的非聚集索引(只有两个键列)

    运行3:使用清单5.1中定义的非聚集索引

    正如我们在以前的级别中所做的那样,我们再次使用读作为主要度量,但是我们也使用SQL Server Management Studio的“显示实际执行计划”选项来查看每个执行的计划。这将给我们一个额外的度量:在非读取活动上花费的工作量的百分比,例如在读入内存之后匹配相关数据。这使我们更好地理解了查询的总成本。

    测试第一个查询:活动总数按产品

    我们的第一个查询,如清单5.2所示,是一个为特定产品提供活动总数的查询。

    SELECT  ProductID ,
            ModifiedDate ,
            SUM(OrderQty) AS 'No of Items' ,
            AVG(UnitPrice) 'Avg Price' ,
            SUM(LineTotal) 'Total Value'
    FROM    Sales.SalesOrderDetail
    WHERE   ProductID = 888
    GROUP BY ProductID ,
            ModifiedDate ;

    清单5.2:“产品的活动总数”查询

    因为索引可以影响查询的性能,但不能影响结果;针对这三种不同的索引方案执行此查询总是会产生以下行集:

    ProductID修改日期不为所有行Avg价格总值

    ----------- ------------    ----------- -----------------------------
    888         2003-07-01      16          602.346           9637.536000
    888         2003-08-01      13          602.346           7830.498000
    888         2003-09-01      19          602.346           11444.574000
    888        2003-10-01       2           602.346           1204.692000
    888         2003-11-01      17          602.346           10239.882000
    888         2003-12-01      4           602.346           2409.384000
    888         2004-05-01      10          602.346           6023.460000
    888         2004-06-01      2           602.346           1204.692000

    8行输出从表中的39个“ProductID = 888”行聚合到每个有一个或多个“ProductID = 888”销售的日期的输出行。进行测试的基本方案如清单5.3所示。在运行任何查询之前,确保运行SET STATISTICS IO ON。

    IF EXISTS ( SELECT  1
                FROM    sys.indexes
                WHERE   name = 'FK_ProductID_ModifiedDate'
                        AND OBJECT_ID = OBJECT_ID('Sales.SalesOrderDetail') ) 
        DROP INDEX Sales.SalesOrderDetail.FK_ProductID_ModifiedDate ;
    GO

    ——运行1:在这里执行清单5.2(没有非聚集索引)

    CREATE NONCLUSTERED INDEX FK_ProductID_ModifiedDate
    ON Sales.SalesOrderDetail (ProductID, ModifiedDate) ;

    ——运行2:在这里重新执行清单5.2(非集群索引,不包含任何内容)

    IF EXISTS ( SELECT  1
                FROM    sys.indexes
                WHERE   name = 'FK_ProductID_ModifiedDate'
                        AND OBJECT_ID = OBJECT_ID('Sales.SalesOrderDetail') ) 
        DROP INDEX Sales.SalesOrderDetail.FK_ProductID_ModifiedDate ;
    GO
     
    CREATE NONCLUSTERED INDEX FK_ProductID_ModifiedDate
    ON Sales.SalesOrderDetail (ProductID, ModifiedDate)
    INCLUDE (OrderQty, UnitPrice, LineTotal) ;

    ——运行3:在这里重新执行清单5.2(包含包含的非聚集索引)

    清单5.3:测试“产品的活动总数”查询

    对每个索引方案执行查询所需的相对工作如表5.1所示。

    1:运行

    没有非聚集索引

    表“SalesOrderDetail”。扫描计数1,逻辑读1238。

    非阅读活动:8%。

    运行2:

    索引-不包括列

    表“SalesOrderDetail”。扫描计数1,逻辑读131。

    非阅读活动:0%。

    运行3:

    包括列

    表“SalesOrderDetail”。扫描计数1,逻辑读3。

    非阅读活动:1%。

    表5.1:使用不同的非聚集索引运行第一个查询的结果三次

    从这些结果可以看出:

    运行1需要对SalesOrderDetail表进行完整的扫描;每一行都必须阅读和检查,以确定是否应该参与结果。

    Run 2使用非聚集索引快速查找39个请求行的书签,但它必须从表中逐个检索这些行。

    运行3在非聚集索引中找到所需的所有内容,并在ProductID内最有利的序列中进行修改。它迅速跳到第一个请求的条目,读了39个连续的条目,在读取的每个条目上做汇总计算,然后完成了。

    测试第二个查询:基于日期的活动总数

    我们的第二个查询与第一个查询完全相同,只是在WHERE子句中发生了更改。这一次,仓库是根据日期请求信息,而不是基于产品。我们必须在最右的搜索键栏上进行过滤,修改日期;而不是最左边的列,ProductID。新的查询如清单5.4所示。

     

    SELECT  ModifiedDate ,
            ProductID ,
            SUM(OrderQty) 'No of Items' ,
            AVG(UnitPrice) 'Avg Price' ,
            SUM(LineTotal) 'Total Value'
    FROM    Sales.SalesOrderDetail
    WHERE   ModifiedDate = '2003-10-01'
    GROUP BY ModifiedDate ,
            ProductID ;

    清单5.4:“按日期执行的活动总数”查询

    产生的行集,部分是:

    产品的修改日期不包括价格总额

    ----------- ------------    ----------- --------------------- ----------------
                                       :
                                       :
    782         2003-10-01      62          1430.9937             86291.624000
    783         2003-10-01      72          1427.9937             100061.564000
    784         2003-10-01      52          1376.994              71603.688000
    792         2003-10-01      12          1466.01               17592.120000
    793         2003-10-01      46          1466.01               67436.460000
    794         2003-10-01      37          1466.01               54242.370000
    795         2003-10-01      22          1466.01               32252.220000
                                       :
                                       :
    (164 row(s) affected)

    WHERE子句将表过滤到1492行;在分组时,生成了164行输出。

    要运行测试,请遵循清单5.3中描述的相同方案,但是使用清单5.4中的新查询。结果是针对每个索引方案执行查询所需的相对工作,如表5.2所示。

    1:运行

    没有非聚集索引

    表“SalesOrderDetail”。扫描计数1,逻辑读1238。

    非阅读活动:10%。

    运行2:

    索引-不包括列

    表“SalesOrderDetail”。扫描计数1,逻辑读1238。

    非阅读活动:10%。

    运行3:

    包括列

    表“SalesOrderDetail”。扫描计数1,逻辑读761。

    非阅读活动:8%。

    表2:使用不同的非聚集索引运行第二个查询的结果

    第一次和第二次测试都产生了相同的计划;一个完整的扫描详细信息表。由于第4级中详细讨论的原因,WHERE子句没有足够的选择性从非覆盖索引中获益。而且,包含任何一个组的行分布在整个表中。在读取表时,每一行必须与组相匹配;以及消耗处理器时间和内存的操作。

    第三个测试在非聚集索引中找到了它所需要的一切;但是,与前面的查询不同,它没有发现索引中相邻的行。在索引中,包含每个组的行是连续的;但这些组织本身分散在指数的长度上。因此,SQL Server扫描索引。

    扫描索引而不是表格有两个优点:

    该指数小于表,要求更少的读数。

    这些行已经分组,需要更少的非读活动。

    结论

    包含的列使非聚集索引能够成为各种查询的索引,从而提高这些查询的性能;有时会很显著。包含的列增加了索引的大小,但在开销方面却没有增加。任何时候创建非聚集索引,尤其是在外键列上,都要问自己:“在这个索引中应该包含哪些额外的列?”

    本文是通往SQL Server索引楼梯的楼梯的一部分

  • 相关阅读:
    delphi 开发扩展(二)
    ubuntu 下的两个项目管理工具
    jquery 图片轮询
    SSL on Ubuntu 8.10 Apache2
    netbeans 字体美化
    用IDHTTP 实现上传和返回XML
    windows7 安装 virtualbox和 ubuntu
    线程加载返回的XMLtoTClientDataSet
    双buffer与单buffer
    西西吹雪:从程序员到项目经理(一)
  • 原文地址:https://www.cnblogs.com/1-1-1-1-2/p/7991532.html
Copyright © 2011-2022 走看看