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

时间:2016-11-18 00:03:55

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

我在名为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'

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

enter image description here

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

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

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

Clustered Index being used

有人可以解释为什么优化引擎正在使用(庞大的)非聚集索引而不是(首选)聚簇索引吗?

  

Microsoft SQL Server 2016(RTM-GDR)(KB3194716) - 13.0.1722.0(X64)
  Windows 10 Pro 6.3(Build 14393:)上的Developer Edition(64位)

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

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

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

  1. CLUSTERED INDEX [CI_EMail_ShopperNumID]
  2. 非集群索引[nci_wi_Shopper_D8E9A1BB0660D0838F923BB8587C7115]
  3. NONCLUSTERED INDEX [NCI_EMail_ShopperNumID]
  4. 现在,当我运行相同的SELECT

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

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

    似乎优化器坚持使用Non Clustered Index,无论如何!

4 个答案:

答案 0 :(得分:2)

正在使用非聚集索引,因为它已针对根据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;

答案 1 :(得分:1)

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

您的聚簇索引和非聚集索引都具有电子邮件地址的b树结构。因此,要么能够非常快速地找到匹配的电子邮件地址。

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

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

但是,在您的情况下,非聚集索引是包含所有列的覆盖索引。也许更多这些适合索引页面而不是数据页面(数据页面上有一些开销,它可能不仅仅是索引页面上的开销)。

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

答案 2 :(得分:0)

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

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

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

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

答案 3 :(得分:0)

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

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

如果您的应用程序需要根据电子邮件地址查找记录,那么为您需要的列创建最小的完整覆盖索引将为您提供最佳性能,这就是nci_wi_Shopper_D8E9A1BB0660D0838F923BB8587C7115所显示的内容。