SQL Server 查詢性能優化——創建索引原則(二)
三:索引的建立原則
一般來說,建立索引要看數據使用的場景,換句話來說哪些訪問數據的SQL語句是常用的,而這些語句是否因為缺少索引(也有可能是索引過多)變的效率低下。但絕不是所有的SQL語句都要建立索引,如果所有的SQL語句都建立索引,那麽可能導致建立過多的索引。
我碰到過每秒鐘新增記錄超過千條的案例,雖然該數據表僅有聚集索引,但因為已存在的鍵值字段的值和新增數據鍵值字段的值並不是按順序遞增,每次新增記錄時,肯定造成整體數據行的重新排列。在移掉聚集索引後,性能約提升20%。也曾經碰到過一個數據表上有20個索引,結果新增一條記錄需要耗時4秒鐘才能完成。
另一個案例中,POS系統中的銷售數據變更,要同時更新多個數據表,每個數據表都有數十萬條記錄以上,但所使用的WHERE字段沒有有效的索引,除查找緩慢外,導致更新時產生了大量的鎖定。各數據表加上應有的索引後,原來要幾十秒的更新,不到一秒鐘便完成了。
另外,根據數據庫的使用時機,也有可能先建立索引,用完後再刪除。例如,當你做年報表、季報表時需要大量查詢各種數據,可以考慮在生成報表之前建立相關索引。但在報表生成完畢後,為了保證平時新增、修改和刪除操作的運行效率,再刪除為了生成報表所建立的相關的索引。
而針對SQL語句或視圖是否值得建立索引的問題,則有以下幾個可以參考的方面。
第一、選擇性
選擇性表示符合你查詢條件的記錄占總記錄的百分比,也就是
選擇性=符合查詢條件的記錄數量/總記錄數量
這個值越小越好,越小代表選擇越高,越適合采用索引。例如 :
select * from WBK_Goods_Info where COP_G_NO=‘00078027‘
在WBK_Goods_Info 表內符合這個條件的記錄只有1條,而整個數據表有100000條記錄,因此該查詢的選擇性是1/100000,這代表非常高的選擇性,如此通過索引來查找數據才有效率。反過來說,或你的語句如下:
Select * from WBK_Goods_Info with(index(idx_cop_g_no)) where COP_G_NO>‘00018000‘
這時符合查詢記錄達82000條,選擇性變為82000/100000,說明選擇性非常低,除非以COP_G_NO字段為鍵值建立的索引是聚集索引,否則如果采用非聚集索引來訪問,反而變成需要讀取至少82000次數據頁,因為SQL SERVER在讀取每一條記錄時,都是先將整個數據頁讀取(請記住,這是SQL SERVER讀取數據的基本單位),再從中取出目標記錄。就算兩條記錄存在同一數據頁上,也要讀該數據頁兩次。因此在選擇性很低時,通過非聚集索引訪問是非常沒有效率的訪問方式,還不如直接進行全表掃描。
第二、數據密度
數據密度是指鍵值惟一的記錄條數分之一,也就是
數據密度=1/鍵值惟一的記錄數量
通過以下語句進行測試:在WBK_PDE_LIST_ORG_HISTROY數據表的COP_G_NO字段建立索引,而後通過dbcc show_statistics語句查詢存儲系統內關於該索引的統計信息的記錄,而後再應用上方的公式,以測試是否與存儲在系統內的ALL Density字段值是否相同:
--創建索引
CREATE NONCLUSTERED INDEX [idx_WBK_PDE_LIST_COP_G_NO] ON [dbo].[WBK_PDE_LIST_ORG_HISTROY]
(
[COP_G_NO] ASC
)
INCLUDE ( [WBOOK_NO],[G_NO],[CODE_T],[UNIT_1],[TRADE_TOTAL],[GROSS_WT])
--返回all desity字段的值
DBCC SHOW_STATISTICS (‘WBK_PDE_LIST_ORG_HISTROY‘,‘idx_WBK_PDE_LIST_COP_G_NO‘)
--計算all desity字段的值
Select 1.0/(select count(distinct COP_G_NO) from WBK_PDE_LIST_ORG_HISTROY) [All Density]
當數據密度越小,也就是惟一性越高時,代表該字段越適合建立索引,因為當總數據條數乘上該密度值,就是一般平均查詢到的記錄數字。
第三、數據分布
數據分布代表多條數據記錄組成的方式,與密度的概念有關。它代表數據記錄是平均散布在一段範圍內,還是集中在部分區塊。其分布示意圖如下圖。
以我們的範例而言,每一種貨物的貨物編號都是自增且惟一的,也就是貨物信息表(wbk_goods_info)中有100000種貨物,以2000的倍數為值域的切分點,則各數據範圍內的記錄條數是相等的,此種分布稱為平均分布。或數據類型如此,則要計算某個查詢條件的選擇性是否很高就相當的容易且精準。
如果數據是標準分布的,也就是說數據在有些範圍內多,有些範圍內少,以這個例子來說,就是有些貨物的銷售記錄很多,有些貨物可能基本上沒有銷售記錄,則該索引就需要有更細致的統計數據,以記錄一個範圍的數據約略有多少條記錄,在查詢優化程序判斷某個索引是否適用某項查詢時,才可以精確判斷出該字段的選擇性是否很高,以決定使用的索引。
這也就是當你觀察Dbcc show_statistics時(如上圖),如果呈現的分布記錄有很多條,表示該鍵值在整個記錄中是標準分布,所以需要各區段的記錄數目,以較為精確地判斷符合條件的記錄數多少,若只有寥寥三四筆,表示接近平均分布,只需要描述平均分布的狀態即可。
第四、索引的有效性
在根據以上三原則建立相應的索引之後,我們再來看看如何觀察在建立索引後,查詢語句是否變得較有效率,索引的使用效率是否高。
--沒有索引的情況
Set statistics io on
Select [WBOOK_NO] ,[COP_G_NO] ,[G_NO]
,[CONTR_ITEM] ,[CODE_S] ,[CODE_T]
,[G_NAME] ,[G_MODEL] ,[G_QTY] ,[G_UNIT] ,[QTY_1] ,[UNIT_1] ,[TRADE_CURR] ,[DECL_PRICE] ,[TRADE_TOTAL] ,[GROSS_WT] ,[NET_WT] from WBK_PDE_LIST_ORG_HISTROY c
Where c.WBOOK_NO=‘BE404942450001‘ and c.COP_G_NO=‘60196928‘ and QTY_1>15
Select * from sys.dm_db_missing_index_groups
Select * from sys.dm_db_missing_index_group_stats
Select * from sys.dm_db_missing_index_details
Select mig.*,statement as table_name,column_id,column_name,column_usage
From sys.dm_db_missing_index_details as mid
Cross apply sys.dm_db_missing_index_columns (mid.index_handle)
Inner join sys.dm_db_missing_index_groups as mig on mig.index_handle=mid.index_handle
Order by mig.index_group_handle,mig.index_handle,column_id
---在建立索引之後,再次執行以上語句。
接下來通過sys_dm_db_index_usage_stats可觀察是否生成了過多的索引。
--插入數據會影響到索引
insert WBK_PDE_LIST_ORG_HISTROY
Select ‘BE404942451001‘,‘60196928‘,‘11427‘,‘305‘,‘92‘,‘52083200‘
,null ,‘布料‘,null,‘215‘,25,‘011‘,25,‘011‘,null,null,null,10.82,270.5,null,null,null,5,3.8
表‘WBK_PDE_LIST_ORG_HISTROY‘。掃描計數0,邏輯讀取17 次,物理讀取5 次,預讀0 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預讀0 次。
--更新數據會影響到索引
--通過PK_WBK_PDE_LIST_ORG_HISTROY
--idx_WBK_PDE_LIST_QTY1
--idx_WBK_PDE_LIST_COP_G_NO索引掃描WBOOK_NO=‘BE404942451001‘的記錄
update WBK_PDE_LIST_ORG_HISTROY set QTY_1=50000
where WBOOK_NO=‘BE404942451001‘
--表‘WBK_PDE_LIST_ORG_HISTROY‘。掃描計數1,邏輯讀取9 次,物理讀取0 次,預讀0 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預讀0 次。
Select * from sys.dm_db_index_usage_stats where object_id=object_id(‘WBK_PDE_LIST_ORG_HISTROY‘)
圖1,索引使用情況分布圖
圖2,索引名稱
從上圖1中可以看到 sys.dm_db_index_usage_stats系統視圖是指某個查詢利用索引所進行的查找、掃描、查找或更新操作都被計為對該索引的一次使用,每次使用都會對視圖中的相應計數器累加1。它針對用戶提交的查詢所導致的操作,以及由系統內部產生的查詢所導致的操作(例如,掃描以收集統計數據)分開累積信息。而由於前述的insert語句會影響到之前建立的所有索引,所以index_id等於1、6、10的記錄行的user_updates字段為是1 (見圖1中2)。update 語句會更新數據表中的QTY_1字段,但是沒有更新COP_G_NO字段,所以只影響index_id等於1與6的記錄行,這兩行的user_updates字段是2(見圖1中3)。update語句的where條件則會利用index_id等於1的索引,見user_seeks的值為1(見圖1中3)。
User_updates字段是指由於基礎數據表或視圖的插入、更新或刪除操作導致的更新次數。利用這個數據可判斷應用程序是否很少用到某個索引。如果該索引的更新次數(user_updates)值很大,那麽說明產生的維護量比較大,再參見搜索次數(user_seeks)與書簽查找操作的次數(user_lookups),如是這兩個值很小,則可以考慮刪除索引。
重新啟動SQL SERVER服務時,sys.dm_db_index_usage_stats系統視圖內的各種計數器會初始化為空值。此外,每當分離或關閉數據時(例如,由於 AUTO_CLOSE 設置為 ON),就會刪除所有與該數據庫關聯的數據行。初次使用某個索引後,才會加入到系統的統計信息中,sys.dm_db_index_usage_stats隨後才看得到代表該索引的數據行,此時各項計數器的初始設置值為零。
最後再次重申一下,“水可載舟,亦可覆舟”,索引也一樣。索引有助於提高檢索性能,但過多或不當的索引也會導致系統低效。因為用戶在表中每加進一個索引,數據庫就要做更多的工作。過多的索引甚至會導致索引碎片。
所以說,我們要建立一個“適當”的索引體系,特別是對聚集索引的創建,更應精益求精,以使您的數據庫能得到高性能的發揮。
因為非聚集索引需要在非聚集索引的B樹中找到每一行的指針,再去其所在表上找數據,性能因此會大打折扣,有時甚至不如不加非聚集索引。
SQL Server 查詢性能優化——創建索引原則(二)