【问题标题】:Understanding Indexes and Missing Index Recommendations in SSMS了解 SSMS 中的索引和缺失索引建议
【发布时间】:2015-07-24 12:45:58
【问题描述】:

了解索引和缺失的索引建议

我正在尝试更好地了解索引。我有很多阅读要做,并且从其他 SO 帖子中找到了许多有价值的资源,其中一些我已经阅读过,其他一些我仍然需要阅读。与此同时,我正试图从我的数据库中获得更好的性能。

我了解到覆盖索引的性能将比单个列上的索引更好,因此我决定从删除我的单个索引开始,并让建议的查询执行计划推荐索引。

SSMS 索引建议

CREATE NONCLUSTERED INDEX IX_my_index_name
ON [dbo].[my_table] ([field_a],[field_b])
INCLUDE (
   [field_1]
  ,[field_2]
  ,[field_3]
  ,[field_4]
  ,[field_5]
  ,[field_6]
)

表格详情

字段 1-6 是我常用来连接我正在使用的 2 个表的列。在我运行的几个耗时查询的 where 子句中可以找到字段 a 和 b。

我理解使用字段 1-6,因为在大多数情况下它们都包含许多不同的值,但 field a 只有大约 75 个不同的值,field b 只有 3 个不同的值。这是在一个包含 70MM 记录的表中。

请注意,这是一个堆。该表上的所有记录都来自另一个具有主键的表,因此它带有唯一值,但它没有设置为该表上的键或唯一索引。 SSMS 不建议将该列包含在此索引中。想知道我应该如何处理进入此表的唯一值?我猜是一个聚集的唯一索引?

我的问题

  1. 我想了解这个索引推荐背后的逻辑。鉴于有关 a 和 b 列中相似值的信息,为什么建议这样做?

  2. 我想了解ON 列和INCLUDE 列之间的区别?

【问题讨论】:

    标签: sql sql-server sql-server-2012


    【解决方案1】:

    索引中的 ON 列可用于搜索行。这些字段包含在索引树中。找到行后,如果需要任何其他列,例如选择部分或连接中的字段,则必须从表中获取它们。这在执行计划中称为key lookup

    如果索引有多个列,并且不是所有列都在 where 子句中指定,则只要给出字段,就可以从一开始就使用这些列。比如索引有A、B、C、D字段,where子句有A、B、D字段,那么只能用A和B取数据。

    如果表有聚集索引,则聚集索引中键的值存储在其他索引中,用于从表本身中查找行。如果没有聚集索引,则以类似的方式使用 RID(行 ID)来定位表中的行。

    索引中的包含列是附加列,它们的数据存储在非聚集索引的叶级。这样 SQL Server 可以直接从那里读取数据并跳过读取表的整个部分。这称为covering index

    【讨论】:

      【解决方案2】:

      我要问的第一件事是,这样大小的表是否有充分的理由没有聚集索引?聚集键甚至不必是唯一的(如果不是,SQL Server 会为其添加一个“唯一标识符”,尽管通常最好使用 IDENTITY 列)。

      回答你的两个问题:

      1) 索引建议与您正在运行的查询有关。根据经验,建议的列将与查询优化器用来探测表的列相匹配,因此如果您有如下查询:

      SELECT field1, field2, field3
      FROM   table1
      WHERE  field4 = 1 AND field5 = 'bob'
      

      建议的索引可能位于field4field5 列上,并按选择性顺序排列(即,值变化最大的列在前)。它可能包括其他列(例如field1, field2, field3),因为这样查询优化器只需访问索引即可获取该数据,而无需访问数据页面。

      另请注意,有时建议的索引并不总是您自己选择的索引。如果连接多个表,查询优化器将根据可用的索引和统计信息选择它认为最适合数据的执行计划。它可能会遍历一个表并探测另一个表,而最好的计划可能会以相反的方式进行。您必须检查实际的查询执行计划以了解发生了什么。

      如果您知道您的查询具有足够的选择性,可以深入到小范围的记录(例如,有一个类似 WHERE table1.field1 = 1 AND table1.field2 = 'abc' AND table1.field3 = '2015-07-01' ... 的 where 子句),您可以添加一个涵盖所有引用列的索引。这可能会影响查询优化器扫描此索引以获取少量行以连接到另一个表,而不是执行扫描。

      根据经验,在检查执行计划时,一个很好的起点是尝试消除扫描,在这种情况下,服务器将读取大量行,并提供索引来缩小必须执行的数据量进行处理。

      2) 我认为其他人现在可能已经很好地解释了这一点 - 包含的列在那里,因此当读取索引时,服务器不必读取数据页来获取这些值;它们也存储在索引中。

      很多人在阅读到此类“覆盖索引”时的最初反应可能是“我为什么不添加一大堆这样做的索引”,或者“我为什么不添加一个索引覆盖所有列”。

      在某些情况下(通常是具有窄列的小表,例如多对多连接表),这很有用。但是,您添加的每个索引都会带来一些成本:

      首先,每次更新或向表中插入值时,都必须更新索引。这意味着您将不得不应对锁定、锁定升级问题(可能是死锁)、页面拆分和相关的碎片。有多种方法可以缓解这些问题,例如使用适当的填充因子来允许将更多值插入到索引页面中,而无需拆分它。

      其次,索引占用空间。至少,索引将包含您使用的键值以及 RID(在堆中)或聚集键(在具有聚集索引的表中)。覆盖索引还包含包含列的副本。如果这些是大列(例如大 varchars),那么索引可能会非常大,并且表索引加起来大于表本身并不是闻所未闻的。请注意,索引的大小也有限制,包括列和总大小。因为聚集键总是包含在具有聚集索引的表的非聚集索引中(聚集索引在数据页本身上),这意味着聚集键越小越好。虽然您可以使用复合索引,但这可能是几个字节宽,虽然您可以使用非唯一键,但 SQL Server 会向其中添加该唯一符,即另外 4 个字节。最佳实践是使用标识列(如果您设想表中的行数超过 20 亿,则为 int 或 bigint)。标识也总是递增,因此在插入新记录时不会在数据页中出现页面拆分,因为它始终位于表的末尾。

      所以 tl;dr;是:

      建议的索引可能很有用,但通常不会给出最佳索引。如果您知道数据的结构以及如何查询数据,则可以构建包含常用探测键的索引。

      始终按照选择性的顺序对索引中的列进行排序(即具有最多值的列在前)。这可能看起来违反直觉,但它允许 SQL Server 以更少的读取速度更快地找到您想要的数据。

      包含的列很有用,但通常只在它们是小列时(例如整数)。如果您的查询需要一个表中的六列,而索引只覆盖其中的五列,SQL Server 仍然需要访问数据页,所以在这种情况下,最好不要包含包含的列,因为它们只占用空间并且具有维护成本。

      【讨论】:

        【解决方案3】:

        包含索引(或覆盖索引)允许 SQL Server 在索引本身中查找满足查询所需的所有信息,而无需返回实际数据页来获取请求的信息。它是数据的副本,但包含部分中的列不用于搜索 - 仅用于返回数据。您的表应该始终(除非在非常非常特殊的情况下,例如当您进行大量插入时)在其上具有聚集索引。

        使用索引的原因是为了减少 SQL Server 必须读取整个表才能返回数据的扫描次数。通过使用索引,SQL Server 可以只查找和读取返回您请求的行所需的页面。如果列的值数量有限,SQL Server 可能会决定忽略索引并进行扫描。您必须查看生成的查询计划以查看 SQL Server 是否使用索引。如果 SQL Server 建议使用索引,则通常意味着 SQL Server 引擎将使用该索引。但是每个索引都有成本——它需要维护,所以不要创建太多索引。

        【讨论】:

          【解决方案4】:

          我想了解这个索引推荐背后的逻辑。鉴于有关字段 a 和 b 中相似值的信息,为什么建议这样做?

          tl;dr 这完全取决于您查询数据的方式。

          这是一个很难回答的问题,因为它取决于您查询表的频率、查询类型、服务器负载以及许多其他因素。

          例如,如果您在许多使用“简单计划”运行的查询中使用字段 1-6,则 SQL Server 不会认为它们是索引的良好候选者。 “简单计划”或“简单计划”是 SQL SERVER 为它认为不够复杂而无法生成完整计划的任何查询提供的计划。

          SQL SERVER 将“充分探索”长时间运行的查询。将计算和存储直方图。这会提醒 SQL SERVER 现有索引不够用。 “完全探索”计划是 SQL SERVER 已扩展并为其生成非简单查询计划的计划。

          我想了解 ON 列和 INCLUDE 列之间的区别?

          On 和 Include 之间的区别是。

          On 语句会将该列包含在索引中。这意味着该列在存储时是索引的一部分。

          所以索引是这样工作的。表的索引形成 B 树。 B-Tree 中的节点包含集群索引值,以及其余值的 ROWID。如果您在 B-Tree 中搜索不属于集群索引的值,它将首先找到集群索引,然后它将具有用于其余数据的内存地址。然后它将对该内存地址进行第二次查找以获取其他值。

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

          【讨论】:

          • 索引没有其余数据的内存地址(它甚至可能不存在于内存中)。它具有聚集键的值,并使用这些值从聚集索引中查找行
          • @JamesZ。好点,我用 ROWID 替换了内存地址。我的理解是 ROWID 与内存地址相同。它们有何不同?
          • 哦,是的,如果是堆,它当然是 RID,BOL 说“行标识符 (RID),由文件号、数据页号和页面上的插槽组成”——我假设在那里是它与缓冲池中地址之间的某种映射,这就是 RID 查找比键查找快的原因。
          猜你喜欢
          • 1970-01-01
          • 2020-10-09
          • 2018-03-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-09-12
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多