前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >包含列的索引:SQL Server索引进阶 Level 5

包含列的索引:SQL Server索引进阶 Level 5

作者头像
Woodson
发布2018-07-19 16:08:54
2.3K0
发布2018-07-19 16:08:54
举报
文章被收录于专栏:学习有记学习有记

作者David Durant,2011/07/13

关于系列

本文属于Stairway系列:Stairway to SQL Server Indexes

索引是数据库设计的基础,并告诉开发人员使用数据库关于设计者的意图。 不幸的是,当性能问题出现时,索引往往被添加为事后考虑。 这里最后是一个简单的系列文章,应该使他们快速地使任何数据库专业人员“快速”。


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

  • 表中每一行的索引总是有一个条目(我们注意到这个规则的一个例外将在后面的级别中进行讨论)。 这些条目始终处于索引键序列中。
  • 在聚集索引中,索引条目是表的实际行。
  • 在非聚集索引中,条目与数据行分开; 由索引键列和书签值组成,以将索引键列映射到表的实际行。

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

包括列

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

创建非聚集索引时,我们指定了与键列分开的包含列; 如清单5.1所示。

代码语言:javascript
复制
CREATE NONCLUSTERED INDEX FK_ProductID_ ModifiedDate
       ON Sales.SalesOrderDetail (ProductID, ModifiedDate)
       INCLUDE (OrderQty, UnitPrice, LineTotal)

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

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

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

代码语言:javascript
复制
ProductID   ModifiedDate   Bookmark

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列时,索引如下所示:

代码语言:javascript
复制
:- Search Key Columns -:      :---  Included Columns  ---:     : Bookmark :

ProductID   ModifiedDate      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的五行在索引中是连续的,每隔一个ProductID / ModifiedDate组合的行也是如此。

你可能会问“为什么甚至包括列?为什么不简单地将OrderQty,UnitPrice和LineTotal添加到索引键?“索引中有这些列但索引键中没有这些列有几个优点,例如:

  • 不属于索引键的列不会影响索引内条目的位置。这反过来又减少了让他们在索引中的开销。例如,如果行中的ProductID或ModifiedDate值被修改,那么该行的条目必须在索引内重新定位。但是,如果行中的UnitPricevalue被修改,索引条目仍然需要更新,但不需要移动。
  • 在索引中查找条目所需的努力较少。
  • 指数的大小会略小。
  • 索引的数据分布统计将更容易维护。

当我们查看索引的内部结构以及由SQL Server维护的用于优化查询性能的一些附加信息时,大多数这些优势在以后的级别中将更有意义。

确定索引列是否是索引键的一部分,或只是包含的列,不是您将要做的最重要的索引决定。也就是说,频繁出现在SELECT列表中但不在查询的WHERE子句中的列最好放在索引的包含列部分。

成为覆盖指标

在级别4中,我们表示与AdventureWorks数据库的设计者达成协议,决定将SalesOrderID / SalesOrderDetailID作为SalesOrderDetail表的聚集索引。针对此表的大多数查询都将请求按销售订单编号排序或分组的数据。然而,可能来自仓库人员的一些查询将需要产品序列中的信息。这些查询将受益于清单5.1所示的索引。

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

  • 运行1:没有非聚集索引
  • 运行2:使用不包含列的非聚簇索引(只有两个关键列)
  • 运行3:使用清单5.1中定义的非聚集索引

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

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

清单5.2中显示的第一个查询是按特定产品的日期提供活动总计的查询。

代码语言:javascript
复制
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:“按产品的活动总计”查询

由于索引可以影响查询的性能,但不影响结果; 对这三个不同的索引方案执行这个查询总是产生下面的行集合:

代码语言:javascript
复制
ProductID   ModifiedDate    No of Rows  Avg Price         Total Value

----------- ------------    ----------- -----------------------------
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

这八行输出从表中的三十九个“ProductID = 888”行聚合而成,每个日期有一个或多个“ProductID = 888”销售的输出行。进行测试的基本方案是 如代码5.3所示。 在运行任何查询之前,请确保您运行SET STATISTICS IO ON。

代码语言:javascript
复制
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

--RUN 1: Execute Listing 5.2 here (no non-clustered index)

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

--RUN 2: Re-execute Listing 5.2 here (non-clustered index with no include)

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) ;

--RUN 3: Re-execute Listing 5.2 here (non-clustered index with include)

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

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

Run 1: No Nonclustered Index

Table 'SalesOrderDetail'. Scan count 1, logical reads 1238. Non read activity: 8%.

Run 2:

Index – No Included Columns Table 'SalesOrderDetail'. Scan count 1, logical reads 131. Non read activity: 0%.

Run 3:

With Included Columns Table 'SalesOrderDetail'. Scan count 1, logical reads 3. Non read activity: 1%.

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

正如你可以从这些结果看到的:

  • 运行1需要完整扫描SalesOrderDetail表; 每一行都必须阅读和检查,以确定是否应该参与结果。
  • 运行2使用非聚集索引为39个请求的行快速查找书签,但它必须从表中单独检索每个行。
  • 运行3在非聚集索引中找到了所需的所有内容,并以最有利的顺序 - 产品ID中的ModifiedDate。 它迅速跳到第一个要求的条目,阅读了39个连续的条目,对每个条目进行了总计算,读取完成。

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

我们的第二个查询与第一个查询是相同的,除了WHERE子句的更改。 这次仓库正在根据日期而不是产品请求信息。 我们必须过滤最右边的搜索键列ModifiedDate; 而不是最左边的一列ProductID。 新的查询如清单5.4所示。

代码语言:javascript
复制
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:“按日期的活动总计”查询

生成的行集部分是:

代码语言:javascript
复制
ProductID   ModifiedDate    No of Items Avg Price             Total Value
----------- ------------    ----------- --------------------- ----------------
                                   :
                                   :
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显示了对每个索引方案执行查询所需的相对工作量。

Run 1:

No Nonclustered Index Table 'SalesOrderDetail'. Scan count 1, logical reads 1238. Non read activity: 10%.

Run 2:

With Index – No Included Columns Table 'SalesOrderDetail'. Scan count 1, logical reads 1238. Non read activity: 10%.

Run 3:

With Included Columns Table 'SalesOrderDetail'. Scan count 1, logical reads 761. Non read activity: 8%.

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

第一次和第二次测试都是相同的计划。对SaleOrderDetail表的完整扫描。由于第4级中详细说明的原因,WHERE子句没有足够的选择性从非覆盖索引中受益。而且,包含任何一个组的行都散布在整个表格中。正在读表时,每一行都必须与其组相匹配。以及消耗处理器时间和内存的操作。

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

扫描索引而不是表格有两个好处:

  • 索引小于表,需要更少的读取。
  • 行已经分组,需要较少的非阅读活动。

结论

包含的列使非聚集索引能够覆盖各种查询的索引,从而提高这些查询的性能; 有时相当戏剧性。 包含的列增加了索引的大小,但在开销方面增加了很少的内容。 每当你创建一个非聚集索引,特别是在一个外键列上时,问问自己 - “我应该在这个索引中包含哪些额外的列?


本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2017年11月29日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 关于系列
  • 包括列
  • 成为覆盖指标
  • 测试第一个查询:产品的活动总数
  • 测试第二个查询:基于日期的活动总数
  • 结论
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档