[英]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
:
現在,當我運行相同的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.