【问题标题】:SQL Server making use of Non Clustered Index despite having a Clustered Index尽管有聚集索引,SQL Server 仍使用非聚集索引
【发布时间】:2017-04-01 17:22:07
【问题描述】:

我在名为Shopper 的表上有两个索引。

聚集索引:

CREATE CLUSTERED INDEX [CI_EMail_ShopperNumID] 
ON [dbo].[Shopper] ([EMail] ASC, [ShopperNumID] ASC)

非聚集索引

CREATE NONCLUSTERED INDEX [nci_wi_Shopper_D8E9A1BB0660D0838F923BB8587C7115] 
ON [dbo].[Shopper] ([EMail] ASC)
INCLUDE ([DateCreated], [FirstName], [LastLoginDate], [LastName],
    [MaxEmailVolume], [ShopperNumID], [ShopperSourceCD], [ShopperSourceOther]) 

我运行了一个非常简单的SELECT:

SELECT ShopperNumID
FROM shopper
WHERE Email = '87.kl@abcxyz.com'

在分析执行计划时,我注意到正在使用非聚集索引:

现在,我删除非聚集索引:

DROP INDEX IF EXISTS [nci_wi_Shopper_D8E9A1BB0660D0838F923BB8587C7115] 
ON [dbo].[Shopper]
GO

然后重新运行我的选择以注意到聚集索引(最终)正在被使用

.

谁能解释一下为什么优化引擎使用的是(庞大的)非聚集索引,而不是(首选的)聚集索引?

Microsoft SQL Server 2016 (RTM-GDR) (KB3194716) - 13.0.1722.0 (X64)
Windows 10 Pro 6.3(内部版本 14393:)上的开发者版(64 位)

更新: 根据收到的输入,为了进一步评估,我在表上创建了另一个非聚集索引,与现有的聚集索引非常相似。

CREATE NONCLUSTERED INDEX [NCI_EMail_ShopperNumID] 
ON [dbo].[Shopper] ([EMail] ASC, [ShopperNumID] ASC)

目前该表有3个索引可以支持我的SELECT:

  1. 聚集索引 [CI_EMail_ShopperNumID]
  2. 非聚类索引 [nci_wi_Shopper_D8E9A1BB0660D0838F923BB8587C7115]
  3. 非集群索引 [NCI_EMail_ShopperNumID]

现在,当我运行相同的SELECT:

SELECT ShopperNumID
FROM shopper
WHERE Email = '87.kl@abcxyz.com'

分析执行计划,我注意到新创建的非聚集索引正在被使用:

无论如何,优化器似乎都坚持使用非聚集索引!

【问题讨论】:

  • 聚集索引并不是真正的索引,它是表本身。因此,它包含所有表列,索​​引列仅定义存储数据的排序顺序。
  • 您的非集群覆盖索引的用例是什么?鉴于它只包含 [email] 列,并且假设它存储在同一个文件组上,它不会比 CLUSTERED 索引的性能有所提高。
  • 我很好奇 - 哪一个实际上更快? (清除缓存后)。 SET STATISTICS IO 是否显示任何特别不同的输出?
  • @Nick.McDermaid - Table 'Shopper'. Scan count 1, logical reads 3, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. 使用任一索引时,STATISTICS IO 输出相同。理想情况下,无论使用什么索引,执行时间都不会受到影响,因为正在寻找(而不是扫描)值。

标签: sql sql-server indexing sql-execution-plan


【解决方案1】:

正在使用非聚集索引,因为它已针对基于Email 查找行进行了优化。

您可能认为它很庞大,但它以Email 为键这一事实使其非常适合您的查询,即使它包括表中的每一列。

您可能没有意识到聚集索引同样庞大,因为它隐含地包含了表中的每个字段。因此,在最坏的情况下(不要设计这样的东西),您的两个索引都以Email 为键,并且都包含每一列。优化器可以选择使用任何一个,真的。

如果您使用此脚本,它可以显示非聚集索引和聚集索引实际使用了多少空间:

SELECT o.NAME AS TableOrViewName,
        i.name As IndexName,
        i.type_desc As IndexType,
        i.index_id As IndexOrdinal,
        s.Name AS SchemaName,
        p.rows AS RowCounts,
        p.data_compression_desc As CompressionType,
        SUM(a.total_pages) * 8 / 1024.0 AS ObjectSpaceMB, 
        SUM(a.used_pages) * 8 / 1024.0 AS UsedSpaceMB
      FROM sys.objects As o
      LEFT JOIN sys.indexes i ON o.OBJECT_ID = i.object_id
      JOIN sys.partitions p ON i.object_id = p.OBJECT_ID AND i.index_id = p.index_id
      JOIN sys.allocation_units a ON p.partition_id = a.container_id
      LEFT JOIN sys.schemas s ON o.schema_id = s.schema_id
      WHERE o.NAME NOT LIKE 'dt%' 
        AND o.is_ms_shipped = 0
        AND i.OBJECT_ID > 255 
      GROUP BY o.Name, 
        i.name, 
        i.type_desc, 
        i.index_id,
        s.Name, 
        p.data_compression_desc,
        p.Rows;

【讨论】:

    【解决方案2】:

    基本上,它是一个中的六个或另一个中的六个。

    您的聚集索引和非聚集索引都具有电子邮件地址的 b 树结构。因此,两者都可以非常快速地找到匹配的电子邮件地址。

    那么,优化器如何选择要获取的内容?好吧,在这两种情况下,如果有一条记录,则获取一页(数据页或索引叶页)。也许选择非聚集索引是任意的。

    但是,优化器不知道一个电子邮件地址匹配多少条记录。因此,它必须根据电子邮件匹配的数量做出决定。如果非聚集索引只有两列,那么这将是不费吹灰之力。索引页面将包含更多记录(因为“记录”只有两列),因此与电子邮件匹配的记录将在更少的页面上。

    但是,在您的情况下,非聚集索引是包含所有列的覆盖索引。与数据页相比,索引页上的这些可能更多(数据页上存在一些开销,并且可能比索引页上的开销更大)。

    那么,我们到哪里去了?基本操作是搜索 b 树(两种索引类型相同),然后读取匹配的记录。在大多数情况下,这两个索引结构在这些操作中是相当等价的。 SQL Server 可能略微偏爱非聚集索引,因为索引页上的记录多于数据页上的记录(这是猜测)。

    【讨论】:

    • 感谢您的意见。请参考我在帖子中所做的编辑。
    【解决方案3】:

    来自MSDN: Clustered and Nonclustered Indexes Described:聚集索引根据键值对表或视图中的数据行进行排序和存储。这些是索引定义中包含的列。每个表只能有一个聚集索引,因为数据行本身只能按一种顺序排序。

    非聚集索引覆盖(包括)附加的指定列,因此在引用任何包含的列时不需要返回表。见MSDN:Create Indexes with Included Columns。实际上,非聚集索引就像创建一个包含列的新表,按索引列排序。

    关于您的查询,聚集索引和非聚集索引非常接近相同,唯一的区别是聚集索引另外按 [ShopperNumID] 排序。也许查询优化器正在选择非聚集索引,因为它名义上更合适。在这种情况下,更好的拟合并不一定意味着更好的性能。

    假设聚集索引和非聚集索引都位于同一个存储介质上,您的非聚集索引占用空间但不会提供额外的性能价值。

    【讨论】:

      【解决方案4】:

      首先,感谢您查看查询计划以了解正在使用的索引。查询优化器试图最小化 IO,但它可以做一些有趣的事情。一般来说,非聚集索引比聚集索引小。如果优化器可以看到非聚集索引可以使用更少的读取来回答查询,那么这就是您问题的答案。如果非聚集索引包含表中的所有列,则例外情况。我怀疑这可能是您的问题的重点。

      虽然在聚集索引中使用字符串肯定是有意义的用例,但请记住,聚集索引始终包含在每个非聚集索引中。如果不是唯一的,您希望您的聚集索引较小且具有选择性,看起来 ShopperNumbId 会满足此条件,但我们没有您的完整表。考虑从聚集索引中删除电子邮件地址。

      如果您的应用程序需要根据电子邮件地址查找记录,为您需要的列创建最小的全覆盖索引将为您提供最佳性能,这就是 nci_wi_Shopper_D8E9A1BB0660D0838F923BB8587C7115 的表现。

      【讨论】:

        猜你喜欢
        • 2013-08-20
        • 2012-10-01
        • 2018-05-08
        • 2014-04-27
        • 2013-03-22
        • 2014-11-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多