SQL SERVER 索引優化
——移除鍵查找(書籤查找)或RID查找
今天,我將分享一個快速移除鍵查找或RID查找的技巧。讓我們首先弄明白什麼是鍵查找或者RID查找。請注意,從SQL Server 2005 SP1 及以前的版本,鍵查找被稱爲書籤查找。
當查詢請求少量數據時,SQL Server 優化器將試圖使用在查詢結果列或包含在WHERE語句中列的非聚集索引,檢索數據。如果查詢請求的數據沒有呈現在非聚集索引中,SQL Server 必須返回數據頁去獲取這些列中的數據。無論表是否有聚集索引,查詢仍將到表或聚集索引中檢索數據。
上面的場景中,如果表有聚集索引,其被稱爲鍵查找 (key lookup,或書籤查找bookmark lookup);如果表沒有聚集索引,但是有一個非聚集索引,其被稱爲 RID 查找。這種操作消耗很大。爲優化任何包含鍵查找或RID查找的查詢,其應該從查詢計劃中被移除。有兩種不同的移除鍵查找或RID查找的方法。
在弄清楚這兩種方法之前,我們將創建沒有聚集索引的測試表,激發RID查找。RID 查找是使用提供的行標識符(row identifier ,RID)在堆上的書籤查找。
--創建有幾個列的表 OneIndex
CREATE TABLE OneIndex(ID INT,
FirstName VARCHAR(100),
LastName VARCHAR(100),
City VARCHAR(100));
GO
--向表中插入10萬行記錄
INSERT INTO OneIndex (ID,FirstName,LastName,City)
SELECT TOP 100000 ROW_NUMBER() OVER (ORDER BY a.name) RowID,
'Bob',
CASE WHEN ROW_NUMBER() OVER (ORDER BY a.name)%2 = 1 THEN 'Smith'
ELSE 'Brown' END,
CASE
WHEN ROW_NUMBER() OVER (ORDER BY a.name)%999 = 1 THEN 'Las Vegas'
WHEN ROW_NUMBER() OVER (ORDER BY a.name)%10 = 1 THEN 'New York'
WHEN ROW_NUMBER() OVER (ORDER BY a.name)%10 = 5 THEN 'San Marino'
WHEN ROW_NUMBER() OVER (ORDER BY a.name)%10 = 3 THEN 'Los Angeles'
ELSE 'Houston' END
FROM sys.all_objects a
CROSS JOIN sys.all_objects b;
GO
現在讓我們運行下面的查詢語句,並檢查查詢計劃
因爲表上沒有索引,執行的是全表掃描(Table Scan)。我們將在表上創建一個聚集索引,然後再次檢查執行計劃。
-- 創建聚集索引
CREATE CLUSTERED INDEX [IX_OneIndex_ID] ON [dbo].[OneIndex] (
[ID] ASC
);
現在再次運行如下查詢
SELECT ID, FirstName
FROM OneIndex
WHERE City = 'Las Vegas'
GO
執行計劃清晰的表明,當表上創建聚集索引後,Table Scan 現在轉變爲 Clustered Index Scan。在這兩種情況下,基本表都是完全掃描的,表上沒有搜索。
現在,讓我們看一下查詢的WHERE 語句。直觀上來看,如果我們在表上創建包含WHERE語句中字段列的索引,可能會獲得性能提升。讓我們在表上創建一個非聚集索引,然後再檢查執行計劃。
--創建非聚集索引
CREATE NONCLUSTERED INDEX IX_OneIndex_City
ON dbo.OneIndex(city ASC);
創建非聚集索引後,我們再次執行查詢,並檢查執行計劃
SELECT ID, FirstName
FROM OneIndex
WHERE City = 'Las Vegas'
GO
因爲索引中包含WHERE語句中的字段,SQL Server 查詢執行引擎使用非聚集索引從表中檢索數據。然而,SELECT語句中的字段並不包含於索引中,爲了獲取這些列,引擎必須再次返回基礎表,檢索那些列。這種特定的行爲被稱爲鍵查找(或書籤查找)。
有兩種不同的方法可以解決這類問題。我將同時展示這兩種方法;然而,推薦你使用任何一種方法移除鍵查找。我選擇方法2.
方法1:創建非聚集覆蓋索引
這種方法,我們將創建同時包含SELECT 語句中的字段和WHERE語句中的字段。
CREATE NONCLUSTERED INDEX IX_OneIndex_Cover
ON dbo.OneIndex(City,Firstname,ID);
一旦上面非聚集索引被創建,其覆蓋查詢中的所有列,讓我們運行如下查詢語句,並檢查我們的執行計劃。
SELECT ID, FirstName
FROM OneIndex
WHERE City = 'Las Vegas'
GO
從執行計劃來看,我們可以確認,鍵查找已經被移除,僅僅發生索引查找(index seek)。因爲沒有鍵查找,SQL Server 查詢引擎沒有必要再從數據頁中檢索數據,索引本身就包含了所有需要的數據。
方法2:創建包含列非聚集索引
這裏我們將用於SELECT 語句中的字段加入到包含列中,同時WHERE語句中的字段爲索引字段。在這種方法中,我們將使用SQL Server 2005 開始介紹的新語法。一個包含非鍵列的索引,當查詢中所有列都包含在索引中,可以顯著提升性能。
CREATE NONCLUSTERED INDEX IX_OneIndex_Include
ON dbo.OneIndex(City)
INCLUDE(FirstName,ID);
創建上面的索引後,再次執行查詢,並查看執行計劃
從執行計劃可以看到,這種方法也移除了鍵查找。
比較方法1、方法2的邏輯讀次數,發現,方法2的性能略優於方法1(方法2邏輯讀6次,方法1邏輯讀7次)。這也是我選擇方法2的原因之一,選擇方法2還有如下優點:
-
索引可以超出索引鍵900字節限制
-
索引可以包含不允許作爲鍵列的數據類型列,如varchar(max)、nvarchar(max)或者XML
-
主索引的大小被減小,其提升了索引操作的性能
總體來說,鍵查找、書籤查找或RID查找降低了查詢的性能,我們可以通過使用包含列索引或者覆蓋索引來優化性能。
我將在另一篇文章中介紹一些與優化方法相關的概念。
如果喜歡,可以掃碼關注SQL Server 公衆號,將有更多精彩內容分享: