SQL Server索引管理——索引創建建議和經驗

SQL Server索引管理——索引創建建議和經驗

 

索引創建的建議

  • 檢查WHERE語句和JOIN關聯列

  • 使用窄索引

  • 檢查列的唯一值(基數)

  • 考慮列的順序

  • 考慮索引類型(行索引 VS. 列索引;聚集索引 VS 非聚集索引)

如果一個表的數據較少,小於8KB,所有數據在一頁上,那麼表掃描可能比索引查找更適合

使用窄索引

你可以使用表中的多列組合創建索引。爲獲取最好的性能,使索引中所包含的列儘可能的少。你也應該避免在索引中國使用寬數據類型列。擁有數據類型(CHAR,VARCHAR、NCHAR、NVARCHAR)的列,有時會很寬,就像二進制一樣;除非它們絕對需要,否則儘量減少在索引中使用大尺寸的寬數據類型列。

相比較於寬列索引,窄列索引在一個8KB的頁中,可以容納 更多的行,這有如下影響:

  • 減少I/O(通過讀取較少8KB頁)

  • 使數據庫緩存更有效,因爲SQL Server可以緩存較少的索引頁,因此可以減少內存中索引頁所需要的邏輯讀

  • 減少數據庫的存儲空間

爲理解窄列索引如何影響邏輯讀,創建一個有20行數據,和一個索引,腳本如下:

IF(SELECT OBJECT_ID('t1')) IS NOT NULL
       DROP TABLE dbo.t1;
GO
CREATE TABLE dbo.t1(c1 INT, c2 INT);
WITH Nums AS
       (SELECT 1 AS n
        UNION ALL
        SELECT n+1
        FROM Nums
        WHERE n<20
        )
        INSERT INTO t1(c1,c2)
        SELECT n,2 FROM Nums;
CREATE INDEX i1 ON t1(c1);

因爲索引列爲窄列(INT數據類型爲4字節),所有索引行可以容納在一個8KB的索引頁中。可以使用如下動態視圖確認這個結論:

SELECT
       i.name
       ,i.type_desc
       ,s.page_count
       ,s.record_count
       ,s.index_level
       ,s.index_depth
FROM sys.indexes i
JOIN sys.dm_db_index_physical_stats(DB_ID(N'WideWorldImporters'),OBJECT_ID(N'dbo.t1'),NULL,NULL,'DETAILED') AS s
       ON i.index_id=s.index_id
WHERE i.[object_id]=OBJECT_ID(N'dbo.t1');

爲了理解寬列索引的缺點,修改索引列c1的類型,將其由INT改爲CHAR(500):

DROP  INDEX t1.i1;
ALTER TABLE t1 ALTER COLUMN c1 CHAR(500);
CREATE INDEX i1 ON t1(c1);

INT類型數據的寬度是4字節,CHAR(500)數據類型的寬度是500字節。因爲索引列的較大的寬度,需要兩個索引頁來容納20個索引行。你可以通過再次運行上面動態視圖腳本進行確認,結果如下:

大的索引鍵尺寸增加了索引頁數,因此增加了索引所需要的內存和硬盤。所以總是建議索引鍵列儘可能窄。

檢查列的唯一值

在小範圍可能值的列上創建索引(如性別),對性能沒有益處,因爲查詢優化器將不能使用這種索引有效的減少返回的行數。考慮性別列,僅僅只有兩個唯一值:男和女。當你執行一個在WHERE中有性別列作爲篩選條件的語句時,最終會從表中獲得大量的行(假定男女分佈是平均的),導致了代價昂貴的表掃描或者索引掃描。總是選擇WHERE語句中,有較多唯一行的列(high selectivity),來限制讀取的行數。你應該在那些列上創建索引,幫助優化器讀取較少的結果集。

進一步,當在多個列上創建索引時,通常被稱爲組合索引列順序問題。在某些情況下,使用最多唯一值的列作爲索引的第一列,將更有效的過濾數據。

注意:組合索引列順序的重要性將在後面的“考慮列順序”部分解釋。

從這點上,你可以看到,瞭解需要創建索引的列的唯一值的多少是很重要的,你可以通過執行類似如下腳本,獲得相關的信息,只需要調整表名和列名即可:

SELECT
       COUNT(DISTINCT Gender) AS DistinctColValuse
       ,COUNT(Gender) AS NumberOfRows
       ,COUNT(DISTINCT Gender)*1.0/COUNT(Gender)
FROM [HumanResources].[Employee];

WHERE語句或者JOIN關係中,擁有最多唯一值的列將是索引最好的候選者。

爲了理解唯一值是如何影響索引的使用的,看一下HumanResources.Employee 表的Gender列,如果你運行前面的查詢,你將看到其有290行,只包含2個唯一值,selectivity 爲 0.006,一個僅僅查詢Gender值爲F的查詢如下:

SELECT * FROM HumanResources.Employee with(index(IX_Employee_Test))
WHERE SickLeaveHours=59
       AND Gender='F'
       AND MaritalStatus='M';

執行計劃和IO消耗如下:

數據通過掃描聚集索引(數據存儲)獲得適合條件 Gender='F' 的值。如果你在Gender上創建索引如下:

CREATE INDEX IX_Employee_Test ON HumanResources.Employee(Gender)

並再次運行上面的查詢,其執行計劃不變。列中數據的selectivity不足夠支撐索引被使用,獨立使用。如果使用下面的組合索引

CREATE INDEX IX_Employee_Test ON HumanResources.Employee(Gender,SickLeaveHours,MaritalStatus)
with(drop_existing=on);

再次執行查詢,執行計劃及IO消耗如下:

現在效果要比聚集索引掃描好的多了。一個較爲清晰索引查找操作將蒐集數據的IO操作將近減半。其他的都花費在鍵查找上。

儘管問題中沒有一個單獨的列具有足夠的selectivity,作爲有效的索引,但他們結合在一起,爲優化器提供了足夠的selectivity,從而採用他們提供的索引。

有必要強制使用第一個創建的測試索引,如果你刪除組合索引,再次創建最初的索引,並修改查詢,使用查詢提示,強制使用最初的索引,如下:

SELECT * FROM HumanResources.Employee with(index(IX_Employee_Test))
WHERE SickLeaveHours=59
       AND Gender='F'
       AND MaritalStatus='M';

你看到同樣是索引查找,但是邏輯讀次數多了近20倍。儘管強制優化器選擇索引是可能的,很清晰其不是最優的方法。

在SQL Server 2008以上另一個強制的不同行爲是 FORCESEEK 查詢提示,FORCESEEK使得優化器僅選擇查找的操作。如果我們像下面重寫查詢:

SELECT * FROM HumanResources.Employee with(FORCESEEK)
WHERE SickLeaveHours=59
       AND Gender='F'
       AND MaritalStatus='M';

結果和強制使用索引一樣

限制優化器的選項,強制行爲在某些情形可能有幫助,但是通常,如這裏的結果,其增加了讀,對整個查詢無益。

檢查數據類型

索引數據類型問題,如,整數鍵上的索引搜索很快,因爲其尺寸較小,並且容易在整數(INT)上進行算術運行。你也可以使用其他類型的整數類型,如BIGINT,SMALLINT和TINYINT作爲索引列,然而字符類型,如CHAR、VARCHAR、NCHAR和NVARCHAR ,因爲其需要字符匹配,通常消耗要比整數要高。

假設你想要在一個列上創建索引,你有兩個候選列:一個是INT數據類型,另外一個是CHAR(4)數據類型。儘管在SQL Server中,兩個類型均佔用4個字節,你將仍然選擇整數列作爲索引列。以算術運算操作爲例。CHAR(4)數據類型中的1,實際上是以1開頭,後面跟三個空格的方式存儲的,是這樣四個字節的結合:0x35、0x20、0x20、0x20. CPU不能理解這樣數據如何進行算術操作,因此,首先將其轉化爲整數數據類型,1在整數類型下是這樣存儲的 0x00000001.CPU可以很容易的對這類數據執行算術操作。

當然,大多數時候,你沒有這種在數據類型尺寸相等的情況下進行簡單最優選擇。當設計或創建你的索引時,考慮這個情況。

考慮列順序

索引鍵先按照第一列進行排序,然後再按照下一列包含於前一列的值進行排序。組合索引中的第一列通常被稱爲索引的leading edge。例如,考慮如下表:

如果在表上創建組合索引(c1,c2),那麼索引將排如下:

如上表展示的,數據首先按照組合索引中第一列c1列進行排序。第一列中的各值,數據在按照第二列(c2)進行排序。

因此,組合索引中列的順序是一個影響索引效率的重要因素。你可以這樣考慮:

  • 列唯一值

  • 列寬

  • 列數據類型

例如,假設在表t1上的查詢均和下面的類似:

SELECT * FROM t1 WHERE c2=12;
SELECT * FROM t1 WHERE c2=12 AND c1=11;

一個如(c2,c1)的索引,將對兩個查詢都有益。但是索引(c1,c2)將不適合,因爲它將按照c1列進行初始化排序,然而第一個查詢語句需要數據按照c2列進行排序。

爲了理解索引列順序的重要性,考慮如下例子

SELECT COUNT(OrderQty) NumberOfOrderQtyRows
     ,COUNT(DISTINCT OrderQty) DistinctOrderQtyColValuse
     ,COUNT(DISTINCT OrderQty)*1.0/COUNT(OrderQty) OrderQtySelectivity
    ,COUNT(ProductID) NumberOfProductIDRows
     ,COUNT(DISTINCT ProductID) DistinctProductIDValuse
     ,COUNT(DISTINCT ProductID)*1.0/COUNT(ProductID) SalesProductIDSelectivity
FROM [Sales].[SalesOrderDetail];

WHERE語句或者JOIN關係中,擁有最多唯一值的列將是索引最好的候選者。

爲了理解唯一值是如何影響索引的使用的,看一下[Sales].[SalesOrderDetail]表的QrderQty列,如果你運行上面的腳本,你將看到,它包含41個唯一值,121317行數據,其selectivity是0.0003.而ProductID列包含266個唯一值,其selectivity爲0.002,一個包含QrderQty,ProductID列條件查詢如下:

SELECT  *
FROM [Sales].[SalesOrderDetail]
WHERE ProductID=714 AND OrderQty<=10 and OrderQty>5  ;

其查詢計劃如爲:

在表上創建索引如下:

CREATE INDEX IX_SalesOrderDetail_Test ON [Sales].[SalesOrderDetail](OrderQty,ProductID)

再次執行查詢腳本,執行計劃及開銷如下:

CREATE INDEX IX_SalesOrderDetail_Test ON [Sales].[SalesOrderDetail](ProductID,OrderQty)
WITH(DROP_EXISTING=ON);

可以看到邏輯讀的次數有所降低。《SQL Server 索引優化—— 查詢條件中等於、大於或小於條件在索引中的順序對性能的影響》一文中闡述了索引順序對性能影響的另一種形式,有興趣者可以參考。

對於索引類型的選擇,將在後續文章中給出,敬請期待……

如果喜歡,可以搜索關注 MSSQLServer 公衆號,將有更多精彩內容分享:

                                                                 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章