【问题标题】:Why use the INCLUDE clause when creating an index?为什么在创建索引时使用 INCLUDE 子句?
【发布时间】:2010-11-21 10:25:57
【问题描述】:

在准备 70-433 考试时,我注意到您可以通过以下两种方式之一创建覆盖索引。

CREATE INDEX idx1 ON MyTable (Col1, Col2, Col3)

-- 或者--

CREATE INDEX idx1 ON MyTable (Col1) INCLUDE (Col2, Col3)

INCLUDE 子句对我来说是新的。为什么要使用它?在确定是否创建包含或不包含 INCLUDE 子句的覆盖索引时,您会建议哪些准则?

【问题讨论】:

    标签: sql-server sql-server-2008 sql-server-2005 indexing


    【解决方案1】:

    如果该列不在WHERE/JOIN/GROUP BY/ORDER BY 中,而仅在列列表中,则SELECT 子句是您使用INCLUDE 的地方。

    INCLUDE 子句在最低/叶级别添加数据,而不是在索引树中。 这使得索引更小,因为它不是树的一部分

    INCLUDE columns 不是索引中的键列,因此它们没有排序。 这意味着它对于我上面提到的谓词、排序等并不是很有用。但是,如果您在键列的几行中有残差查找,它可能很有用

    Another MSDN article with a worked example

    【讨论】:

    • 那么,这将是一种创建覆盖索引的低成本版本的技术吗?
    • @gbn,您介意更详细地解释一下这句话,并解释为什么它意味着include子句对排序没有用处等:“INCLUDE子句将数据添加到最低/叶级别,而不是在索引树中。这使得索引更小,因为它不是树的一部分"
    • @JMarsch:很抱歉回复晚了,但是是的,就是这样。
    • @Tola Odejayi:INCLUDE 列不是索引中的键列,因此它们没有排序。这使得它们通常对 JOIN 或排序没有用处。而且因为它们不是关键列,所以它们不像关键列那样位于整个 B 树结构中
    • 虽然这是最被接受的答案,但我认为需要进一步解释,如果对于某些查询,该列是 SELECT 的一部分,而对于某些不是?\
    【解决方案2】:

    您可以使用 INCLUDE 将一列或多列添加到非聚集索引的叶级别,如果这样做可以“覆盖”您的查询。

    假设您需要查询员工的 ID、部门 ID 和姓氏。

    SELECT EmployeeID, DepartmentID, LastName
    FROM Employee
    WHERE DepartmentID = 5
    

    如果您碰巧在 (EmployeeID, DepartmentID) 上有一个非聚集索引,一旦您找到给定部门的员工,您现在必须执行“书签查找”来获取实际的完整员工记录,只是为了得到姓氏列。如果您找到很多员工,这在性能方面可能会变得非常昂贵。

    如果您在索引中包含该姓氏:

    CREATE NONCLUSTERED INDEX NC_EmpDep 
      ON Employee(EmployeeID, DepartmentID)
      INCLUDE (Lastname)
    

    那么您需要的所有信息都在非聚集索引的叶级别中可用。只需在非聚集索引中查找并找到给定部门的员工,您就拥有了所有必要的信息,并且不再需要在索引中找到的每个员工的书签查找 --> 您可以节省大量时间。

    显然,您不能在每个非聚集索引中包含每一列 - 但如果您的查询确实缺少一两列需要“覆盖”(并且经常使用),这可能会很有帮助将它们包含到合适的非聚集索引中。

    【讨论】:

    • 您确定要使用此索引吗?为什么是员工 ID?您只需要键列中的 DepartmentID 吗?您在这里被引用为权威:stackoverflow.com/q/6187904/27535
    • 您的解释很好,但实际上与您概述的用例并不相符。键列应位于过滤器或查询中的 JOIN 键上,INCLUDEs 需要是您正在检索但未排序的数据。
    • 首先索引Employee(EmployeeID, DepartmentID) 不会用于过滤DepartmentID = 5。因为它的顺序不匹配
    【解决方案3】:

    这个讨论遗漏了重要的一点:问题不在于“非关键列”是否更好地包含为 index-columns 或 included-列。

    问题是使用包含机制来包含索引中并不真正需要的列的成本是多少? (通常不是 where 子句的一部分,但通常包含在选择中)。所以你的困境总是:

    1. 在 id1、id2 ... idN 上使用索引单独
    2. 在 id1, id2 ... idN 上使用索引 加上包括 col1, col2 ... colN

    在哪里: id1, id2 ... idN 是经常在限制中使用的列,而 col1, col2 ... colN 是经常选择的列,但通常在限制中使用

    (将所有这些列作为索引键的一部分包含在内的选项总是很愚蠢(除非它们也用于限制) - 因为必须更新和排序索引,因此维护成本总是更高即使“键”没有改变)。

    所以使用选项 1 或 2?

    回答:如果您的表很少更新 - 主要是插入/删除 - 那么使用包含机制包含一些“热列”(通常在选择中使用)相对便宜 - 但 不是 通常用于限制),因为插入/删除无论如何都需要更新/排序索引,因此在已经更新索引的同时存储一些额外的列几乎没有额外的开销。开销是用于在索引上存储冗余信息的额外内存和 CPU。

    如果您考虑添加为包含列的列经常更新(没有更新索引--列) - - 如果它太多其中索引变得接近您的表的副本 - 我建议使用选项 1!此外,如果添加某些包含列结果证明没有性能差异 - 您可能想跳过添加它们的想法:)验证它们是否有用!

    键 (id1, id2 ... idN) 中每个相同值的平均行数也很重要。

    请注意,如果在限制中使用了作为索引的包含列添加的列:只要索引是这样的可以使用(基于对 index-key-columns 的限制) - 然后 SQL Server 将列限制与索引(叶节点值)相匹配,而不是使用昂贵的绕着桌子走。

    【讨论】:

      【解决方案4】:

      基本索引列已排序,但包含的列未排序。这节省了维护索引的资源,同时仍然可以在包含的列中提供数据以覆盖查询。因此,如果您想覆盖查询,您可以将搜索条件用于定位行到索引的已排序列中,然后“包含”具有非搜索数据的其他未排序列。它绝对有助于减少索引维护中的排序和碎片量。

      【讨论】:

        【解决方案5】:

        原因(包括索引的叶级别中的数据)已经很好地解释了。您对此感到震惊的原因是,当您运行查询时,如果您没有包含附加列(SQL 2005 中的新功能),SQL Server 必须转到聚集索引以获取附加列这会花费更多时间,并在将新数据页加载到内存时为 SQL Server 服务、磁盘和内存(具体来说是缓冲区缓存)增加更多负载,从而可能会将其他更经常需要的数据从缓冲区缓存中推出。

        【讨论】:

        • 有没有办法证明它实际上使用了更少的内存?这也是我所期望的,但我在工作中对此有些静态
        • 鉴于您必须将页面从堆或聚集索引加载到内存以及索引页面,这意味着您将重复数据放入内存,数学变得非常简单。至于具体衡量的方法,没有。
        【解决方案6】:

        选择INCLUDE 而不是键列如果您不需要键中的该列 的一个原因是文档。这使得未来不断发展的索引变得更加容易。

        考虑你的例子:

        CREATE INDEX idx1 ON MyTable (Col1) INCLUDE (Col2, Col3)
        

        如果您的查询如下所示,则该索引是最好的:

        SELECT col2, col3
          FROM MyTable
         WHERE col1 = ...
        

        当然,如果您可以从将列放在关键部分中获得额外的好处,那么您不应该将列放在 INCLUDE 中。以下两个查询实际上都更喜欢索引键中的col2 列。

        SELECT col2, col3
          FROM MyTable
         WHERE col1 = ...
           AND col2 = ...
        
        SELECT TOP 1 col2, col3
          FROM MyTable
         WHERE col1 = ...
         ORDER BY col2
        

        假设情况不是,我们在INCLUDE 子句中有col2,因为将它放在索引的树部分没有任何好处。

        快进几年。

        您需要调整此查询:

        SELECT TOP 1 col2
          FROM MyTable
         WHERE col1 = ...
         ORDER BY another_col
        

        要优化该查询,以下索引会很棒:

        CREATE INDEX idx1 ON MyTable (Col1, another_col) INCLUDE (Col2)
        

        如果您检查该表上已有的索引,则您之前的索引可能仍然存在:

        CREATE INDEX idx1 ON MyTable (Col1) INCLUDE (Col2, Col3)
        

        现在您知道Col2Col3 不是索引树的一部分,因此既不用于缩小读取索引范围,也不用于对行进行排序。将another_column 添加到索引键部分的末尾(col1 之后)是相当安全的。破坏任何东西的风险很小:

        DROP INDEX idx1 ON MyTable;
        CREATE INDEX idx1 ON MyTable (Col1, another_col) INCLUDE (Col2, Col3);
        

        那个索引会变大,还是有一定风险的,但是一般来说扩展现有索引比引入新索引要好。

        如果您有一个没有INCLUDE 的索引,您不知道在Col1 之后添加another_col 会破坏哪些查询。

        CREATE INDEX idx1 ON MyTable (Col1, Col2, Col3)
        

        如果在Col1Col2 之间添加another_col 会发生什么?其他查询会受到影响吗?

        INCLUDE 与关键列相比还有其他“好处”如果您添加这些列只是为了避免从表中获取它们。但是,我认为文档方面最重要。

        回答你的问题:

        在确定是否创建包含或不包含 INCLUDE 子句的覆盖索引时,您会提出哪些建议?

        如果您向索引添加列的唯一目的是使该列在索引中可用而不访问该表,请将其放入INCLUDE 子句中。

        如果将列添加到索引键会带来额外的好处(例如,对于order by 或因为它可以缩小读取索引范围)将其添加到键中。

        您可以在此处阅读更长的讨论:

        https://use-the-index-luke.com/blog/2019-04/include-columns-in-btree-indexes

        【讨论】:

          【解决方案7】:

          我在已经给出的答案中没有看到的另一个考虑因素是,包含的列可以是不允许作为索引键列的数据类型,例如 varchar(max)。

          这允许您将此类列包含在覆盖索引中。我最近不得不这样做以提供一个 nHibernate 生成的查询,该查询在 SELECT 中有很多列,并带有一个有用的索引。

          【讨论】:

            【解决方案8】:

            内联到索引定义中的所有列的总大小是有限制的。尽管如此,我从来不需要创建那么宽的索引。 对我来说,更大的优势是您可以使用包含列的一个索引覆盖更多查询,因为它们不必以任何特定顺序定义。想想就是作为索引内的索引。 一个例子是 StoreID(其中 StoreID 是低选择性,这意味着每个商店都与很多客户相关联),然后是客户人口统计数据(姓氏、名字、出生日期): 如果您只是按此顺序(StoreID、LastName、FirstName、DOB)内联这些列,则只能有效地搜索您知道 StoreID 和 LastName 的客户。

            另一方面,在 StoreID 上定义索引并包括 LastName、FirstName、DOB 列将让您在本质上执行两次查找——在 StoreID 上进行索引谓词,然后在任何包含的列上查找谓词。这可以让您涵盖所有可能的搜索排列,只要它以 StoreID 开头。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2014-03-28
              • 1970-01-01
              • 1970-01-01
              • 2012-06-11
              • 1970-01-01
              • 2021-02-07
              • 2011-02-01
              相关资源
              最近更新 更多