簡體   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