索引設計是數據庫設計中比較重要的一個環節,對數據庫的性能其中至關重要的作用,但是索引的設計卻又不是那么容易的事情,性能也不是那么輕易就獲取到的,很多的技術人員因為不恰當的創建索引,最后使得其效果適得其反,可以說“成也索引,敗也索引”
本系列文章來自Stairway to SQL Server Indexes,翻譯和整理發布在agilesharp和博客園,希望對廣大的技術朋友在如何使用索引上有所幫助。
在本篇文章中,我們在學習了之前的知識之后,推薦14條指導方針。這14條指導方針可以幫助你更好的為數據庫構建索引。
本篇文章的格式使用了由Addison Wesley出版社出版的<Framework Design Guidelines>中使用的格式。每一個最佳實踐之前都使用了如下4個動詞:要,考慮,避免、不要,分別代表如下意思:
要(Do):這個原則要堅決遵守
考慮(Consider):通常情況下都要遵循這個原則,但如果你對原則背后的原理有了深入了理解,可以根據實際情況不采用這個原則
避免(Avoid):考慮的反義詞,意味着避免做某這類事,但同樣,如果你了解了背后的原理,則可以根據實際情況做這類事。
不要(Do Not):避免的增強版,意思是無論什么時候都不要做這類事。
指導方針
要了解跑在數據庫上的應用程序/用戶
使用索引的主要目的是為了提高跑在數據庫上應用程序讀取和操作數據的速度,如果你不知道程序主要對數據庫進行什么操作,索引優化就無從談起。
當然,如果你全程參與了程序的設計和開發,那再好不過。但這種情況少之又少,大多數情況都是你直接接手數據庫和應用程序,這時你就需要兩步走的了解你所接手的東西-通過外部和內部。
外部方法包括從用戶那里了解程序相關的信息,觀察他們使用程序的過程,閱讀用戶文檔和交接文檔。
內部方法是去看程序本身對數據庫產生的操作。比如說Activity Monitor, Profiler等工具,也可以使用sys.dm_db_index usage_stats和sys.dm_db_missing_index_XXX系列DMV中找到所需信息,這些信息包括用的最多的查詢,用的最多的索引,用的少的索引以及本應建卻沒有建的索引。
通過找到拖累系統性能的查詢,比如報表服務中用到的語句,agent中執行的T-SQL,SSIS中執行的T-SQL以及存儲過程。找到這類信息就可以知道優化該從何處下手。
得到上面的信息后,就可以知道哪些索引應當存在,哪些索引應該刪除。
不要過度創建索引
過多的索引和太少的索引都不是好事。表中該有多少索引可不是一個固定的數字。當你為主鍵,候選鍵和外鍵建立了索引之后,剩下的索引該怎么建就需要謹慎分析后再做定奪了。
要明白這點:同樣的數據庫在不同的環境下要有不同的索引
在忙時或是閑時;在OLTP環境或是OLAP環境下,所需要的索引是不同的。
比如每天晚上一次性大量更新數據的報表數據庫在這時只需要少量索引,而在日間忙時則需要大量索引。數據庫上跑少量查詢要比數據庫跑大量查詢需要更少的索引。
要給每個表設置主鍵
雖然SQL Server並不強制要求設置主鍵。但一個沒有主鍵的表無論在OLTP還是OLAP環境下都是一件危險的事,因為沒有主鍵就不能保證每行是唯一的。這時你就無法知道同一行數據是否在表中存在兩條,尤其是在你還沒有足夠的信息去分析這點時。
盡管SQL Server不強制要求設置主鍵,但主鍵是關系數據庫的一個關鍵理論。如果沒有主鍵約束,那么與之關聯的唯一索引或是連接操作就有可能產生意料之外的性能問題。
除此之外,很多第三方開發工具或插件也要求表有主鍵,比如說吧,ADO.Net的SqlCommandBuilder和Entity Data Modeler都依賴表中存在主鍵約束。另外,主鍵約束會創建一個同名的唯一索引來保證主鍵的唯一性。
考慮給每個表設置聚集索引
本系列第三篇關於聚集索引的文章闡述了聚集索引帶來的好處。使用聚集索引表中的數據就是按聚集索引鍵的順序存在而不再以堆存放。使用聚集索引的好處是使得數據按照聚集索引鍵的順序存放,並使得后插入的元素依然保持這個順序。
如果你遵循了上一個建議,那么每個表都應該有主鍵,因此,每一個表都應該有一個或多個索引,讓其中的一個索引成為聚集索引。聚集索引本身並不會使得表上多了一個索引,而是讓表的結構更好的組織。
選擇聚集索引鍵時,要記住第六篇文章中所說的,聚集索引鍵應該唯一,短和盡量不需要改動。
考慮使用外鍵作為聚集索引鍵的最左列
將外鍵設置為聚集索引的最左列就是將表中的數據按照這列的值進行匯總和組織,這也是查詢所需。比如說你用信用卡消費這個行為是和卡關聯最強的的,而不是和你刷卡的商場以及處理這筆消費的銀行。則將信用卡號作為消費記錄表中聚集索引的最左列,使得所有同一張卡的消費信息就會存在連續的頁中。
當然了,你還需要另外一個很少變動的列和這個信用卡號列組合起來保證聚集索引鍵的唯一性。
考慮為索引添加包含列
(譯者注:這里作者文章有BUG,這段和上段一樣,我就大膽的寫一下原因吧。)為索引添加包含列的原因是減少對索引所在表的書簽查找。因為包含列不會占用索引的非葉子節點空間,所以不會影響B樹的高度,通過在葉子節點附加上一些列,使得索引更容易的“覆蓋”所請求的查詢,從而減少了書簽查找,降低了查詢成本。
但同樣,使用包含列使得非聚集索引占用的空間增加了,所以使用包含列時要綜合考慮。
避免為重復值很多的未過濾列創建非聚集索引
更早之前的一條建議“永遠不要索引性別列”,是由於這列只會存在男性和女性兩個值。當遇到WHERE Gender=的語句時使用表掃描要遠遠好於書簽查找,查詢優化器無法從這個索引中獲益。
考慮為列中重復值最多的值創建過濾索引
如果某列大量的行中都存在相同的值,這個值可以是NULL,那么使用過濾索引將這個值過濾掉,剩下的值所生成的索引就會更小,小索引使得查詢優化器選擇書簽查找而不是表掃描,SQL Server也就更容易使用索引。
考慮使用填充因子來面對未來的數據增長
假如一個表中只有幾個月的數據,但這個表年底的數據已經可以估算出來時,重建索引的過程中將填充因子設置為7或8,這將使索引占用的頁和年底占用的頁大致相同,這可以更早的暴漏性能問題,比如說表掃描時IO的占用。
考慮使用填充因子來減少頁分裂
加入表中的數據已經達到了頁所能容納的最大值。那么再插入數據就會導致頁分裂了。因此重建索引時可以使用填充因子,如果數據庫寫大於讀的話,設置填充因子為75,如果讀寫大致相等的話,設置填充因子為90到95.
要在創建非聚集索引之前,先創建聚集索引
與之對應的指導方針是:在刪除聚集索引之前,先刪除非聚集索引。如果你不按照這條方針做,則會導致無意義的重建非聚集索引。將表由聚集索引變為堆會使得表上的非聚集索引重建,因為非聚集索引的書簽由聚集索引鍵變為RID。
要根據索引的使用頻率定期整理索引碎片或是重建索引
如果一個索引經常用於掃描,正如我們在第11篇文章中所說,那么外部碎片對於性能的影響就變得非常大。這時你就需要考慮在外部碎片到達10%的時候整理索引了。當外部碎片達到30%時就需要重建索引。對於OLTP環境來說,上面的值是一個分界點,這個點就是整理或重建索引的代價小於其帶來的性能提升。
要經常更新索引的統計信息
關鍵字是“經常”,也就是多久更新一次。通過了解跑在數據庫上面的程序的負載之后,就知道該多久更新一次統計信息了。我已經在第14篇文章中講述了為什么要定期更新索引。
總結
上面的這些最佳實踐是來自多個DBA多年來實踐於不同環境所產生的。遵循這些指導方針可以幫助你創建更好的的索引。