在SQL Server 查詢性能優化——覆蓋索引(一) 中講了覆蓋索引的一些理論。
本文將具體講一下使用不同索引對查詢性能的影響。
下面通過實例,來查看不同的索引結構,如聚集索引、非聚集索引、組合索引等來查看相同的SQL語句查詢的不同性能
例一:沒有任何索引的查詢訪問
1.表的碎片情況:
2.SQL查詢語句與查詢執行計划成本
--要求返回IO統計,也就是數據頁訪問的數量
SET STATISTICS IO ON
--沒有任何索引情況下的數據頁訪問數量
SELECT [WBOOK_NO] ,[COP_G_NO] ,[G_NO] ,[CODE_T] ,[QTY_1] ,[UNIT_1] ,[TRADE_TOTAL] ,[GROSS_WT]
FROM [WBK_PDE_LIST_ORG_HISTROY] where qty_1 between 50 and 500
--表'WBK_PDE_LIST_ORG_HISTROY'。掃描計數1,邏輯讀取1568 次,物理讀取54 次,預讀1568 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預讀 0 次。
SET STATISTICS IO OFF
例二:通過聚集索引查詢訪問
1.聚集索引的碎片情況:
2.SQL查詢語句與查詢執行計划成本
--要求返回IO統計,也就是數據分頁訪問的數量
SET STATISTICS IO ON
---通過聚集索引查詢訪問的數據頁數量
create clustered index idx_WBK_PDE_LIST_ORG_HISTROY on [WBK_PDE_LIST_ORG_HISTROY](QTY_1) SELECT [WBOOK_NO] ,[COP_G_NO] ,[G_NO] ,[CODE_T] ,[QTY_1] ,[UNIT_1] ,[TRADE_TOTAL] ,[GROSS_WT]
FROM [WBK_PDE_LIST_ORG_HISTROY] where qty_1 between 50 and 500
--表'WBK_PDE_LIST_ORG_HISTROY'。掃描計數1,邏輯讀取351 次,物理讀取4 次,預讀345 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預讀0 次。
SET STATISTICS IO OFF
---
drop index [WBK_PDE_LIST_ORG_HISTROY].idx_WBK_PDE_LIST_ORG_HISTROY ---
例三:強制通過非聚集索引查詢訪問
1.非聚集索引的碎片情況:
2.SQL查詢語句與查詢執行計划成本
--要求返回IO統計,也就是數據頁訪問的數目
SET STATISTICS IO ON
--強制通過非聚集索引查詢訪問的數據頁數量,用錯索引比不用索引更糟糕
create index idx_WBK_PDE_LIST_ORG_HISTROY on [WBK_PDE_LIST_ORG_HISTROY](WBOOK_NO) SELECT [WBOOK_NO] ,[COP_G_NO] ,[G_NO] ,[CODE_T] ,[QTY_1] ,[UNIT_1] ,[TRADE_TOTAL] ,[GROSS_WT]
FROM [WBK_PDE_LIST_ORG_HISTROY] with (index(idx_WBK_PDE_LIST_ORG_HISTROY)) where qty_1 between 50 and 500
--表'WBK_PDE_LIST_ORG_HISTROY'。掃描計數1,邏輯讀取61065 次,物理讀取864 次,預讀727 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預讀0 次。
SET STATISTICS IO OFF
---
drop index [WBK_PDE_LIST_ORG_HISTROY].idx_WBK_PDE_LIST_ORG_HISTROY
例四:通過字段順序不適用的覆蓋索引查詢訪問
1.非聚集索引的碎片情況:
2.SQL查詢語句與查詢執行計划成本
--要求返回IO統計,也就是數據頁訪問的數量
SET STATISTICS IO ON
--通過字段順序不適用的覆蓋索引查詢訪問的數據頁數量
create index idx_WBK_PDE_LIST_ORG_HISTROY on [WBK_PDE_LIST_ORG_HISTROY]([WBOOK_NO] ,[COP_G_NO] ,[G_NO] ,[CODE_T] ,[QTY_1] ,[UNIT_1] ,[TRADE_TOTAL] ,[GROSS_WT]) SELECT [WBOOK_NO] ,[COP_G_NO] ,[G_NO] ,[CODE_T] ,[QTY_1] ,[UNIT_1] ,[TRADE_TOTAL] ,[GROSS_WT]
FROM [WBK_PDE_LIST_ORG_HISTROY] where qty_1 between 50 and 500
--表'WBK_PDE_LIST_ORG_HISTROY'。掃描計數1,邏輯讀取687 次,物理讀取9 次,預讀683 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預讀0 次。
SET STATISTICS IO OFF
---
drop index [WBK_PDE_LIST_ORG_HISTROY].idx_WBK_PDE_LIST_ORG_HISTROY
例五:通過覆蓋索引查詢訪問
1.非聚集索引的碎片情況:
2.SQL查詢語句與查詢執行計划成本
--要求返回IO統計,也就是數據頁訪問的數量
SET STATISTICS IO ON
--通過覆蓋索引查詢訪問的數據頁數量
create index idx_WBK_PDE_LIST_ORG_HISTROY on [WBK_PDE_LIST_ORG_HISTROY]([QTY_1] ,[COP_G_NO] ,[G_NO] ,[CODE_T] ,[WBOOK_NO] ,[UNIT_1] ,[TRADE_TOTAL] ,[GROSS_WT]) SELECT [WBOOK_NO] ,[COP_G_NO] ,[G_NO] ,[CODE_T] ,[QTY_1] ,[UNIT_1] ,[TRADE_TOTAL] ,[GROSS_WT]
FROM [WBK_PDE_LIST_ORG_HISTROY] where qty_1 between 50 and 500
--表'WBK_PDE_LIST_ORG_HISTROY'。掃描計數1,邏輯讀取178 次,物理讀取5 次,預讀175 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預讀0 次。
SET STATISTICS IO OFF
---
drop index [WBK_PDE_LIST_ORG_HISTROY].idx_WBK_PDE_LIST_ORG_HISTROY
例六:通過字段順序不適用的覆蓋索引查詢訪問
1.聚集索引的碎片情況:
2.SQL查詢語句與查詢執行計划成本
--要求返回IO統計,也就數據頁訪問的數量
SET STATISTICS IO ON
---通過字段順序不適用的覆蓋索引查詢訪問的數據頁數量
create index idx_WBK_PDE_LIST_ORG_HISTROY on [WBK_PDE_LIST_ORG_HISTROY]([WBOOK_NO]) include(qty_1 ,[COP_G_NO] ,[G_NO] ,[CODE_T] ,[UNIT_1] ,[TRADE_TOTAL] ,[GROSS_WT]) SELECT [WBOOK_NO] ,[COP_G_NO] ,[G_NO] ,[CODE_T] ,[QTY_1] ,[UNIT_1] ,[TRADE_TOTAL] ,[GROSS_WT]
FROM [WBK_PDE_LIST_ORG_HISTROY] where qty_1 between 50 and 500
--表'WBK_PDE_LIST_ORG_HISTROY'。掃描計數1,邏輯讀取682 次,物理讀取1 次,預讀492 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預讀0 次。
SET STATISTICS IO OFF
---
drop index [WBK_PDE_LIST_ORG_HISTROY].idx_WBK_PDE_LIST_ORG_HISTROY
例七:通過子葉層覆蓋索引查詢訪問(INCLUDE)
1.聚集索引的碎片情況:
2.SQL查詢語句與查詢執行計划成本
--要求返回IO統計,也就是數據頁訪問的數量
SET STATISTICS IO ON
--通過子葉層覆蓋索引查詢訪問的數據頁數量
create index idx_WBK_PDE_LIST_ORG_HISTROY on [WBK_PDE_LIST_ORG_HISTROY](qty_1) include([WBOOK_NO] ,[COP_G_NO] ,[G_NO] ,[CODE_T] ,[UNIT_1] ,[TRADE_TOTAL] ,[GROSS_WT]) SELECT [WBOOK_NO] ,[COP_G_NO] ,[G_NO] ,[CODE_T] ,[QTY_1] ,[UNIT_1] ,[TRADE_TOTAL] ,[GROSS_WT]
FROM [WBK_PDE_LIST_ORG_HISTROY] where qty_1 between 50 and 500
--表'WBK_PDE_LIST_ORG_HISTROY'。掃描計數1,邏輯讀取177 次,物理讀取4 次,預讀173 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預讀0 次。
SET STATISTICS IO OFF
drop index [WBK_PDE_LIST_ORG_HISTROY].idx_WBK_PDE_LIST_ORG_HISTROY
訪問方式分頁 |
邏輯讀 |
物理讀 |
預讀 |
估計運算符開銷 |
全表掃描 |
1568 |
54 |
1568 |
1.06575 |
以QTY_1字段建立聚集索引 |
351 |
4 |
345 |
0.275863 |
以WBOOK_NO字段建非立聚集索引 |
61065 |
864 |
727 |
14.10295 |
以[WBOOK_NO],[COP_G_NO],[G_NO],[CODE_T],[QTY_1],[UNIT_1],[TRADE_TOTAL] ,[GROSS_WT]八個字段建復合索引 |
687 |
9 |
683 |
0.570198 |
以[QTY_1],[COP_G_NO],[G_NO],[CODE_T],[WBOOK_NO],[UNIT_1],[TRADE_TOTAL],[GROSS_WT]八個字段建復合索引 |
178 |
5 |
175 |
0.146974 |
以WBOOK_NO建立索引,include以下字段 [QTY_1],[COP_G_NO],[G_NO],[CODE_T],[UNIT_1],[TRADE_TOTAL],[GROSS_WT] |
682 |
1 |
492 |
0.570198 |
以[QTY_1]建立索引,include 以下字段[WBOOK_NO],[COP_G_NO],[G_NO],[CODE_T],[UNIT_1],[TRADE_TOTAL],[GROSS_WT] |
177 |
4 |
173 |
0.146974 |
例一/例二/例三/例四/例五/例六/例七 |
8.8/2/345/3.9/1/3.9/1 |
54/4/864/9/5/1/4 |
8.8/2/4.1/3.9/1/2.8 |
7.2/1.9/96/3.9/1/3.9/1 |
從上表中可以得出一個結論,如果索引使用不當,例如上面的例三——強制使用選擇性很低的索引來查找數據(或是索引統計數據錯誤、優化引擎誤判等,造成索引使用不當),反而會導致大量的I/O操作(邏輯讀61065次,物理讀864次),其成本比進行全表掃描(例一)還高。
例二,通過聚集索引來查找,因為縮小了數據表掃描范圍,所以效果較佳。
例五、例七,建立覆蓋索引,因為數據結構遠小於數據表本身,所以不管組合索引的字段順序是否正確,都有更好的查詢效果。當然 ,依WHERE條件所需要的字段建立索引數據擺放順序,也就是[QTY_1]放在索引順序的第一位,再include查詢所需要的字段([WBOOK_NO],[COP_G_NO],[G_NO],[CODE_T],[UNIT_1],[TRADE_TOTAL],[GROSS_WT]),其查詢性能最佳。
最后要提醒注意:
1) 在建立覆蓋查詢時要盡量限制索引鍵值的大小,保持Row-to-key的大小比例差異越大越好。否則掃描覆蓋索引與掃描數據表所花的I/O操作差不多,這樣就失去了建立覆蓋索引的意義。
2) 覆蓋索引可以用來提升查詢性能,因為索引中包含了所有查詢里的列.非聚集索引為表里的每一行用索引鍵值來存儲一行。另外SQL Server能使用索引頁級里的這些行來執行聚集計算。這意味着SQLServer不必去實際的表執行聚集計算,這樣可以提升性能。
3) 覆蓋索引能提升獲取數據的性能,但它們也能降低INSERT、UPDATE和DELETE操作的性能。這是因為維護覆蓋索引要求做一些額外的工作。通常這不是問題,除非你的數據庫經常進行非常高的INSERT、UPDATE和DELETE操作。你也許不得不在你的產品系統上應用覆蓋索引之前,要先進行實驗,看看你所建立的覆蓋索引是否在提升性能方面上比影響性能方面更有幫助。
4) 應該在那些SELECT查詢中常使用到的列上創建覆蓋索引,但覆蓋索引中包括過多的列也不行,因為覆蓋索引列的值是存儲在內存中的,這樣會消耗過多內存,引發性能下降。
關於索引碎片的修復:
關於上圖的一些說明:
avg_fragmentation_in_percent:邏輯碎片(索引中的無序頁)的百分比。這是索引的葉級頁中出錯頁所占的百分比。對於出錯頁,分配給索引的下一個物理頁不是由當前葉級頁中的“下一頁”指針所指向的頁。
fragment_count : 索引中的碎片(物理上連續的葉頁)數量。
avg_fragment_size_in_pages :索引中一個碎片的平均頁數。
知道索引碎片程度后,可以使用下表確定修復碎片的最佳方法。
avg_fragmentation_in_percent 值 |
修復語句 |
> 5% 且 < = 30% |
ALTER INDEX REORGANIZE |
> 30% |
ALTER INDEX REBUILD WITH (ONLINE = ON) |