繁体   English   中英

SQL Server尽管具有聚集索引,但仍使用非聚集索引

[英]SQL Server making use of Non Clustered Index despite having a Clustered Index

我在名为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 :)上的Developer Edition(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. NONCLUSTERED索引[NCI_EMail_ShopperNumID]

现在,当我运行相同的SELECT

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

并分析执行计划,我注意到正在使用新创建的非聚集索引: 在此处输入图片说明

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

使用非聚集索引是因为它针对基于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;

基本上是六分之一或六分之一。

您的聚集索引和非聚集索引都具有电子邮件地址的b树结构。 因此,任何一个都可以很快找到匹配的电子邮件地址。

那么,优化器如何选择要提取的内容? 很好,在两种情况下,如果只有一条记录,那么将获取一页(数据页或索引叶页)。 选择非聚簇索引可能是任意的。

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

但是,在您的情况下,非聚集索引是所有列的覆盖索引。 也许适合索引页的数据量比数据页的数据量大(数据页上有一些开销,而且可能比索引页上的开销更多)。

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

MSDN:群集索引和非群集索引描述 :群集索引根据键或索引对数据行进行排序并将其存储在表或视图中。 这些是索引定义中包含的列。 每个表只能有一个聚集索引,因为数据行本身只能以一种顺序排序。

非聚集索引覆盖(包括)其他指定的列,因此在引用任何包含的列时都不需要返回到表。 请参见MSDN:使用包含的列创建索引 实际上,非聚集索引就像使用包含的列创建一个新表,并按索引列进行排序。

对于您的查询,聚簇索引和非聚簇索引几乎相同,唯一的区别是聚簇索引还按[ShopperNumID]排序。 也许查询优化器选择了非聚集索引,因为它名义上更合适。 在这种情况下,更好的配合不一定意味着更好的性能。

假设聚簇索引和非聚簇索引都位于同一存储介质上,则非聚簇索引会占用空间,但不会提供附加的性能值。

首先,赞美查看查询计划以查看正在使用什么索引。 查询优化器试图最小化IO,但是它可以做一些有趣的事情。 一般而言,非聚集索引小于聚集索引。 如果优化器看到非聚集索引可以使用较少的读取次数来回答查询,那么这就是您问题的答案。 例外是非聚集索引包含表中的所有列。 我怀疑这可能是您的问题的重点。

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM