原文地址:http://tech.it168.com/a2009/1125/814/000000814758_all.shtmlhtml
我之因此先從索引談起是由於採用正確的索引會使生產系統的性能獲得質的提高,另外一個緣由是建立或修改索引是在數據庫上進行的,不會涉及到修改程序,並能夠當即見到成效。數據庫
咱們仍是溫習一下索引的基礎知識吧,我相信你已經知道什麼是索引了,但我見到不少人都還不是很明白,我先給你們將一個故事吧。服務器
好久之前,在一個古城的的大圖書館中珍藏有成千上萬本書籍,但書架上的書沒有按任何順序擺放,所以每當有人詢問某本書時,圖書管理員只有挨個尋找,每一次都要花費大量的時間。app
[這就比如數據表沒有主鍵同樣,搜索表中的數據時,數據庫引擎必須進行全表掃描,效率極其低下。]函數
更糟的是圖書館的圖書愈來愈多,圖書管理員的工做變得異常痛苦,有一天來了一個聰明的小夥子,他看到圖書管理員的痛苦工做後,想出了一個辦法,他建議將每本書都編上號,而後按編號放到書架上,若是有人指定了圖書編號,那麼圖書管理員很快就能夠找到它的位置了。工具
[給圖書編號就象給表建立主鍵同樣,建立主鍵時,會建立彙集索引樹,表中的全部行會在文件系統上根據主鍵值進行物理排序,當查詢表中任一行時,數據庫首先使用匯集索引樹找到對應的數據頁(就象首先找到書架同樣),而後在數據頁中根據主鍵鍵值找到目標行(就象找到書架上的書同樣)。]性能
因而圖書管理員開始給圖書編號,而後根據編號將書放到書架上,爲此他花了整整一天時間,但最後通過測試,他發現找書的效率大大提升了。測試
[在一個表上只能建立一個彙集索引,就象書只能按一種規則擺放同樣。]優化
但問題並未徹底解決,由於不少人記不住書的編號,只記得書的名字,圖書管理員無賴又只有掃描全部的圖書編號挨個尋找,但此次他只花了20分鐘,之前未給圖書編號時要花2-3小時,但與根據圖書編號查找圖書相比,時間仍是太長了,所以他向那個聰明的小夥子求助。spa
[這就好像你給Product表增長了主鍵ProductID,但除此以外沒有創建其它索引,當使用Product Name進行檢索時,數據庫引擎又只要進行全表掃描,逐個尋找了。]
聰明的小夥告訴圖書管理員,以前已經建立好了圖書編號,如今只須要再建立一個索引或目錄,將圖書名稱和對應的編號一塊兒存儲起來,但這一次是按圖書名稱進行排序,若是有人想找「Database Management System」一書,你只須要跳到「D」開頭的目錄,而後按照編號就能夠找到圖書了。
因而圖書管理員興奮地花了幾個小時建立了一個「圖書名稱」目錄,通過測試,如今找一本書的時間縮短到1分鐘了(其中30秒用於從「圖書名稱」目錄中查找編號,另外根據編號查找圖書用了30秒)。
圖書管理員開始了新的思考,讀者可能還會根據圖書的其它屬性來找書,如做者,因而他用一樣的辦法爲做者也建立了目錄,如今能夠根據圖書編號,書名和做者在1分鐘內查找任何圖書了,圖書管理員的工做變得輕鬆了,故事也到此結束。
到此,我相信你已經徹底理解了索引的真正含義。假設咱們有一個Products表,建立了一個彙集索引(根據表的主鍵自動建立的),咱們還須要在ProductName列上建立一個非彙集索引,建立非彙集索引時,數據庫引擎會爲非彙集索引自動建立一個索引樹(就象故事中的「圖書名稱」目錄同樣),產品名稱會存儲在索引頁中,每一個索引頁包括必定範圍的產品名稱和它們對應的主鍵鍵值,當使用產品名稱進行檢索時,數據庫引擎首先會根據產品名稱查找非彙集索引樹查出主鍵鍵值,而後使用主鍵鍵值查找彙集索引樹找到最終的產品。
下圖顯示了一個索引樹的結構
圖 1 索引樹結構
它叫作B+樹(或平衡樹),中間節點包含值的範圍,指引SQL引擎應該在哪裏去查找特定的索引值,葉子節點包含真正的索引值,若是這是一個彙集索引樹,葉子節點就是物理數據頁,若是這是一個非彙集索引樹,葉子節點包含索引值和彙集索引鍵(數據庫引擎使用它在彙集索引樹中查找對應的行)。
一般,在索引樹中查找目標值,而後跳到真實的行,這個過程是花不了什麼時間的,所以索引通常會提升數據檢索速度。下面的步驟將有助於你正確應用索引。
確保每一個表都有主鍵
這樣能夠確保每一個表都有彙集索引(表在磁盤上的物理存儲是按照主鍵順序排列的),使用主鍵檢索表中的數據,或在主鍵字段上進行排序,或在where子句中指定任意範圍的主鍵鍵值時,其速度都是很是快的。
在下面這些列上建立非彙集索引:
1)搜索時常用到的;
2)用於鏈接其它表的;
3)用於外鍵字段的;
4)高選中性的;
5)ORDER BY子句使用到的;
6)XML類型。
假設你在Sales表(SelesID,SalesDate,SalesPersonID,ProductID,Qty)的外鍵列(ProductID)上建立了一個索引,假設ProductID列是一個高選中性列,那麼任何在where子句中使用索引列(ProductID)的select查詢都會更快,若是在外鍵上沒有建立索引,將會發生所有掃描,但還有辦法能夠進一步提高查詢性能。
假設Sales表有10,000行記錄,下面的SQL語句選中400行(總行數的4%):
咱們來看看這條SQL語句在SQL執行引擎中是如何執行的:
1)Sales表在ProductID列上有一個非彙集索引,所以它查找非彙集索引樹找出ProductID=112的記錄;
2)包含ProductID = 112記錄的索引頁也包括全部的彙集索引鍵(全部的主鍵鍵值,即SalesID);
3)針對每個主鍵(這裏是400),SQL Server引擎查找彙集索引樹找出真實的行在對應頁面中的位置;
SQL Server引擎從對應的行查找SalesDate和SalesPersonID列的值。
在上面的步驟中,對ProductID = 112的每一個主鍵記錄(這裏是400),SQL Server引擎要搜索400次彙集索引樹以檢索查詢中指定的其它列(SalesDate,SalesPersonID)。
若是非彙集索引頁中包括了彙集索引鍵和其它兩列(SalesDate,,SalesPersonID)的值,SQL Server引擎可能不會執行上面的第3和4步,直接從非彙集索引樹查找ProductID列速度還會快一些,直接從索引頁讀取這三列的數值。
幸運的是,有一種方法實現了這個功能,它被稱爲「覆蓋索引」,在表列上建立覆蓋索引時,須要指定哪些額外的列值須要和彙集索引鍵值(主鍵)一塊兒存儲在索引頁中。下面是在Sales 表ProductID列上建立覆蓋索引的例子:
應該在那些select查詢中常使用到的列上建立覆蓋索引,但覆蓋索引中包括過多的列也不行,由於覆蓋索引列的值是存儲在內存中的,這樣會消耗過多內存,引起性能降低。
建立覆蓋索引時使用數據庫調整顧問
咱們知道,當SQL出問題時,SQL Server引擎中的優化器根據下列因素自動生成不一樣的查詢計劃:
1)數據量
2)統計數據
3)索引變化
4)TSQL中的參數值
5)服務器負載
這就意味着,對於特定的SQL,即便表和索引結構是同樣的,但在生產服務器和在測試服務器上產生的執行計劃可能會不同,這也意味着在測試服務器上建立的索引能夠提升應用程序的性能,但在生產服務器上建立一樣的索引卻未必會提升應用程序的性能。由於測試環境中的執行計劃利用了新建立的索引,但在生產環境中執行計劃可能不會利用新建立的索引(例如,一個非彙集索引列在生產環境中不是一個高選中性列,但在測試環境中可能就不同)。
所以咱們在建立索引時,要知道執行計劃是否會真正利用它,但咱們怎麼才能知道呢?答案就是在測試服務器上模擬生產環境負載,而後建立合適的索引並進行測試,若是這樣測試發現索引能夠提升性能,那麼它在生產環境也就更可能提升應用程序的性能了。
雖然要模擬一個真實的負載比較困難,但目前已經有不少工具能夠幫助咱們。
使用SQL profiler跟蹤生產服務器,儘管不建議在生產環境中使用SQL profiler,但有時沒有辦法,要診斷性能問題關鍵所在,必須得用,在http://msdn.microsoft.com/en-us/library/ms181091.aspx有SQL profiler的使用方法。
使用SQL profiler建立的跟蹤文件,在測試服務器上利用數據庫調整顧問建立一個相似的負載,大多數時候,調整顧問會給出一些能夠當即使用的索引建議,在http://msdn.microsoft.com/en-us/library/ms166575.aspx有調整顧問的詳細介紹。
你可能已經建立好了索引,而且全部索引都在工做,但性能卻仍然很差,那極可能是產生了索引碎片,你須要進行索引碎片整理。
什麼是索引碎片?
因爲表上有過分地插入、修改和刪除操做,索引頁被分紅多塊就造成了索引碎片,若是索引碎片嚴重,那掃描索引的時間就會變長,甚至致使索引不可用,所以數據檢索操做就慢下來了。
有兩種類型的索引碎片:內部碎片和外部碎片。
內部碎片:爲了有效的利用內存,使內存產生更少的碎片,要對內存分頁,內存以頁爲單位來使用,最後一頁每每裝不滿,因而造成了內部碎片。
外部碎片:爲了共享要分段,在段的換入換出時造成外部碎片,好比5K的段換出後,有一個4k的段進來放到原來5k的地方,因而造成1k的外部碎片。
如何知道是否發生了索引碎片?
執行下面的SQL語句就知道了(下面的語句能夠在SQL Server 2005及後續版本中運行,用你的數據庫名替換掉這裏的AdventureWorks):
執行後顯示AdventureWorks數據庫的索引碎片信息。
圖 3 索引碎片信息
使用下面的規則分析結果,你就能夠找出哪裏發生了索引碎片:
1)ExternalFragmentation的值>10表示對應的索引起生了外部碎片;
2)InternalFragmentation的值<75表示對應的索引起生了內部碎片。
如何整理索引碎片?
有兩種整理索引碎片的方法:
1)重組有碎片的索引:執行下面的命令
ALTER INDEX ALL ON TableName REORGANIZE
2)重建索引:執行下面的命令
ALTER INDEX ALL ON TableName REBUILD WITH (FILLFACTOR=90,ONLINE=ON)
也可使用索引名代替這裏的「ALL」關鍵字重組或重建單個索引,也可使用SQL Server管理工做臺進行索引碎片的整理。
圖 4 使用SQL Server管理工做臺整理索引碎片
何時用重組,何時用重建呢?
當對應索引的外部碎片值介於10-15之間,內部碎片值介於60-75之間時使用重組,其它狀況就應該使用重建。
值得注意的是重建索引時,索引對應的表會被鎖定,但重組不會鎖表,所以在生產系統中,對大表重建索引要慎重,由於在大表上建立索引可能會花幾個小時,幸運的是,從SQL Server 2005開始,微軟提出了一個解決辦法,在重建索引時,將ONLINE選項設置爲ON,這樣能夠保證重建索引時表仍然能夠正常使用。
雖然索引能夠提升查詢速度,但若是你的數據庫是一個事務型數據庫,大多數時候都是更新操做,更新數據也就意味着要更新索引,這個時候就要兼顧查詢和更新操做了,由於在OLTP數據庫表上建立過多的索引會下降總體數據庫性能。
我給你們一個建議:若是你的數據庫是事務型的,平均每一個表上不能超過5個索引,若是你的數據庫是數據倉庫型,平均每一個表能夠建立10個索引都沒問題。
在前面咱們介紹瞭如何正確使用索引,調整索引是見效最快的性能調優方法,但通常而言,調整索引只會提升查詢性能。除此以外,咱們還能夠調整數據訪問代碼和TSQL,本文就介紹如何以最優的方法重構數據訪問代碼和TSQL。
也許你不喜歡個人這個建議,你或你的團隊可能已經有一個默認的潛規則,那就是使用ORM(Object Relational Mapping,即對象關係映射)生成全部SQL,並將SQL放在應用程序中,但若是你要優化數據訪問性能,或須要調試應用程序性能問題,我建議你將SQL代碼移植到數據庫上(使用存儲過程,視圖,函數和觸發器),緣由以下:
一、使用存儲過程,視圖,函數和觸發器實現應用程序中SQL代碼的功能有助於減小應用程序中SQL複製的弊端,由於如今只在一個地方集中處理SQL,爲之後的代碼複用打下了良好的基礎。
二、使用數據庫對象實現全部的TSQL有助於分析TSQL的性能問題,同時有助於你集中管理TSQL代碼。
三、將TS QL移植到數據庫上去後,能夠更好地重構TSQL代碼,以利用數據庫的高級索引特性。此外,應用程序中沒了SQL代碼也將更加簡潔。
雖然這一步可能不會象前三步那樣立竿見影,但作這一步的主要目的是爲後面的優化步驟打下基礎。若是在你的應用程序中使用ORM(如NHibernate)實現了數據訪問例行程序,在測試或開發環境中你可能發現它們工做得很好,但在生產數據庫上卻可能遇到問題,這時你可能須要反思基於ORM的數據訪問邏輯,利用TSQL對象實現數據訪問例行程序是一種好辦法,這樣作有更多的機會從數據庫角度來優化性能。
我向你保證,若是你花1-2人月來完成遷移,那之後確定不止節約1-2人年的的成本。
OK!假設你已經照個人作的了,徹底將TSQL遷移到數據庫上去了,下面就進入正題吧!