數據庫優化實踐【高級索引、反範式篇】

  通過索引優化,重構TSQL後你的數據庫還存在性能問題嗎?徹底有可能,這時必須得找另外的方法才行。SQL Server在索引方面還提供了某些高級特性,可能你還從未使用過,利用高級索引會顯著地改善系統性能,本文將從高級索引技術談起,另外還將介紹反範式化技術。web

  第六步:應用高級索引數據庫

  實施計算列並在這些列上建立索引後端

  你可能曾經寫過從數據庫查詢一個結果集的應用程序代碼,對結果集中每一行進行計算生成最終顯示輸出的信息。例如,你可能有一個查詢從數據庫檢索訂單信息,在應用程序代碼中你可能已經經過對產品和銷售量執行算術操做計算出了總的訂單價格,但爲何你不在數據庫中執行這些操做呢?性能優化

  請看下面這張圖,你能夠經過指定一個公式將一個數據庫表列做爲計算列,你的TSQL在查詢清單中包括這個計算列,SQL引擎將會應用這個公式計算出這一列的值,在執行查詢時,數據庫引擎將會計算訂單總價,併爲計算列返回結果。函數

  圖 1 計算列性能

  使用計算列你能夠將計算工做所有交給後端執行,但若是表的行數太多可能計算性能也不高,若是計算列出如今Select查詢的where子句中狀況會更糟,在這種狀況下,爲了匹配where子句指定的值,數據庫引擎不得不計算表中全部行中計算列的值,這是一個低效的過程,由於它老是須要全表掃描或全彙集索引掃描。優化

  所以問題就來了,如何提升計算列的性能呢?解決辦法是在計算列上建立索引,當計算列上有索引後,SQL Server會提早計算結果,而後在結果之上構建索引。此外,當對應列(計算列依賴的列)的值更新時,計算列上的索引值也會更新。所以,在執行查詢時,數據庫引擎不會爲結果集中的每一行都執行一次計算公式,相反,經過索引可直接得到計算列預先計算出的值,所以在計算列上建立一個索引將會加快查詢速度。spa

  提示:若是你想在計算列上建立索引,必須確保計算列上的公式不能包括任何「非肯定的」函數,例如getdate()就是一個非肯定的函數,由於每次調用它,它返回的值都是不同的。設計

  建立索引視圖代理

  你是否知道能夠在視圖上建立索引?OK,不知道不要緊,看了個人介紹你就明白了。

  爲何要使用視圖?

  你們都知道,視圖自己不存儲任何數據,只是一條編譯的select語句。數據庫會爲視圖生成一個執行計劃,視圖是能夠重複使用的,由於執行計劃也能夠重複使用。

  視圖自己不會帶來性能的提高,我曾經覺得它會「記住」查詢結果,但後來我才知道它除了是一個編譯了的查詢外,其它什麼都不是,視圖根本記不住查詢結果,我敢打賭好多剛接觸SQL的人都會有這個錯誤的想法。

  可是如今我要告訴你一個方法讓視圖記住查詢結果,其實很是簡單,就是在視圖上建立索引就能夠了。

  若是你在視圖上應用了索引,視圖就成爲索引視圖,對於一個索引視圖,數據庫引擎處理SQL,並在數據文件中存儲結果,和彙集表相似,當基礎表中的數據發生變化時,SQL Server會自動維護索引,所以當你在索引視圖上查詢時,數據庫引擎簡單地從索引中查找值,速度固然就很快了,所以在視圖上建立索引能夠明顯加快查詢速度。

  但請注意,天下沒有免費的午飯,建立索引視圖能夠提高性能,當基礎表中的數據發生變化時,數據庫引擎也會更新索引,所以,當視圖要處理不少行,且要求和,當數據和基礎表不常常發生變化時,就應該考慮建立索引視圖。

  如何建立索引視圖?

  1)建立/修改視圖時指定SCHEMABINDING選項:

REATE  VIEW  dbo.vOrderDetails

  
WITH  SCHEMABINDING

  
AS

  
SELECT

  2)在視圖上建立一個惟一的彙集索引;

  3)視須要在視圖上建立一個非彙集索引。

  不是全部視圖上均可以建立索引,在視圖上建立索引存在如下限制:

  1)建立視圖時使用了SCHEMABINDING選項,這種狀況下,數據庫引擎不容許你改變表的基礎結構;

  2)視圖不能包含任何非肯定性函數,DISTINCT子句和子查詢;

  3)視圖中的底層表必須由彙集索引(主鍵)。

  若是你發現你的應用程序中使用的TSQL是用視圖實現的,但存在性能問題,那此時給視圖加上索引可能會帶來性能的提高。

  爲用戶定義函數(UDF)建立索引

  在用戶定義函數上也能夠建立索引,但不能直接在它上面建立索引,須要建立一個輔助的計算列,公式就使用用戶定義函數,而後在這個計算列字段上建立索引。具體步驟以下:

  1)首先建立一個肯定性的函數(若是不存在的話),在函數定義中添加SCHEMABINDING選項,如:

CREATE   FUNCTION   [ dbo.ufnGetLineTotal ]

  (

  
--  Add the parameters for the function here

  
@UnitPrice   [ money ] ,

  
@UnitPriceDiscount   [ money ] ,

  
@OrderQty   [ smallint ]

  )

  
RETURNS   money

  
WITH  SCHEMABINDING

  
AS

  
BEGIN

  
return  ((( @UnitPrice * (( 1.0 ) - @UnitPriceDiscount )) * @OrderQty ))

  
END

  2)在目標表上增長一個計算列,使用前面定義的函數做爲該列的計算公式,如圖2所示。

CREATE   FUNCTION   [ dbo.ufnGetLineTotal ]

  (

  
--  Add the parameters for the function here

  
@UnitPrice   [ money ] ,

  
@UnitPriceDiscount   [ money ] ,

  
@OrderQty   [ smallint ]

  )

  
RETURNS   money

  
WITH  SCHEMABINDING

  
AS

  
BEGIN

  
return  ((( @UnitPrice * (( 1.0 ) - @UnitPriceDiscount )) * @OrderQty ))

  
END
 

圖 2 指定UDF爲計算列的結算公式

  3)在計算列上建立索引

  當你的查詢中包括UDF時,若是在該UDF上建立了以計算列爲基礎的索引,特別是兩個表或視圖的鏈接條件中使用了UDF,性能都會有明顯的改善。

  在XML列上建立索引

  在SQL Server(2005和後續版本)中,XML列是以二進制大對象(BLOB)形式存儲的,可使用XQuery進行查詢,但若是沒有索引,每次查詢XML數據類型時都很是耗時,特別是大型XML實例,由於SQL Server在運行時須要分隔二進制大對象評估查詢。爲了提高XML數據類型上的查詢性能,XML列能夠索引,XML索引分爲兩類。

  主XML索引

  建立XML列上的主索引時,SQL Server會切碎XML內容,建立多個數據行,包括元素,屬性名,路徑,節點類型和值等,建立主索引讓SQL Server更輕鬆地支持XQuery請求。下面是建立一個主XML索引的示例語法。 

CREATE   PRIMARY  XML  INDEX
index_name
ON   < object >  ( xml_column )

  次要XML索引

  雖然XML數據已經被切條,但SQL Server仍然要掃描全部切條的數據才能找到想要的結果,爲了進一步提高性能,還須要在主XML索引之上建立次要XML索引。有三種次要XML索引。

  1)「路徑」(Path)次要XML索引:使用.exist()方法肯定一個特定的路徑是否存在時它頗有用;

  2)「值」(Value)次要XML索引:用於執行基於值的查詢,但不知道完整的路徑或路徑包括通配符時;

  3)「屬性」(Secondary)次要XML索引:知道路徑時檢索屬性的值。

  下面是一個建立次要XML索引的示例:

CREATE  XML  INDEX
index_name
ON   < object >  ( xml_column )
USING XML 
INDEX  primary_xml_index_name
FOR  { VALUE  |  PATH  |  PROPERTY }

  請注意,上面講的原則是基礎,若是盲目地在表上建立索引,不必定會提高性能,由於有時在某些表的某些列上建立索引時,可能會導致插入和更新操做變慢,當這個表上有一個低選中性列時更是如此,一樣,當表中的記錄不多(如<500)時,若是在這樣的表上建立索引反倒會使數據檢索性能下降,由於對於小表而言,全表掃描反而會更快,所以在建立索引時應放聰明一點。

  

第七步:應用反範式化,使用歷史表和預計算列

  反範式化

  若是你正在爲一個OLTA(在線事務分析)系統設計數據庫,主要指爲只讀查詢優化過的數據倉庫,你能夠(和應該)在你的數據庫中應用反範式化和索引,也就是說,某些數據能夠跨多個表存儲,但報告和數據分析查詢在這種數據庫上可能會更快。

  但若是你正在爲一個OLTP(聯機事務處理)系統設計數據庫,這樣的數據庫主要執行數據更新操做(包括插入/更新/刪除),我建議你至少實施第1、2、三範式,這樣數據冗餘能夠降到最低,數據存儲也能夠達到最小化,可管理性也會好一點。

  不管咱們在OLTP系統上是否應用範式,在數據庫上總有大量的讀操做(即select查詢),當應用了全部優化技術後,若是發現數據檢索操做仍然效率低下,此時,你可能須要考慮應用反範式設計了,但問題是如何應用反範式化,以及爲何應用反範式化會提高性能?讓咱們來看一個簡單的例子,答案就在例子中。

  假設咱們有兩個表OrderDetails(ID,ProductID,OrderQty) 和 Products(ID,ProductName)分別存儲訂單詳細信息和產品信息,如今要查詢某個客戶訂購的產品名稱和它們的數量,查詢SQL語句以下:

SELECT  Products.ProductName,OrderQty

  
FROM  OrderDetails  INNER   JOIN  Products

  
ON  OrderDetails.ProductID  =  Products.ProductID

  
WHERE  SalesOrderID  =   47057

  若是這兩個都是大表,當你應用了全部優化技巧後,查詢速度仍然很慢,這時能夠考慮如下反範式化設計:

  1)在OrderDetails表上添加一列ProductName,並填充好數據;

  2)重寫上面的SQL語句

  SELECT  ProductName,OrderQty

  
FROM  OrderDetails

  
WHERE  SalesOrderID  =   47057

  注意在OrderDetails表上應用了反範式化後,再也不須要鏈接Products表,所以在執行SQL時,SQL引擎不會執行兩個表的鏈接操做,查詢速度固然會快一些。

  爲了提升select操做性能,咱們不得不作出一些犧牲,須要在兩個地方(OrderDetails 和 Products表)存儲相同的數據(ProductName),當咱們插入或更新Products 表中的ProductName字段時,不得不一樣步更新OrderDetails表中的ProductName字段,此外,應用這種反範式化設計時會增長存儲資源消耗。

  所以在實施反範式化設計時,咱們必須在數據冗餘和查詢操做性能之間進行權衡,同時在應用反範式化後,咱們不得不重構某些插入和更新操做代碼。有一個重要的原則須要遵照,那就是隻有當你應用了全部其它優化技術都還不能將性能提高到理想狀況時才使用反範式化。同時還需注意不能使用太多的反範式化設計,那樣會使本來清晰的表結構設計變得越來模糊。

  歷史表

  若是你的應用程序中有按期運行的數據檢索操做(如報表),若是涉及到大表的檢索,能夠考慮按期將事務型規範化表中的數據複製到反範式化的單一的歷史表中,如利用數據庫的Job來完成這個任務,並對這個歷史表創建合適的索引,那麼週期性執行的數據檢索操做能夠遷移到這個歷史表上,對單個歷史表的查詢性能確定比鏈接多個事務表的查詢速度要快得多。

  例如,假設有一個連鎖商店的月度報表須要3個小時才能執行完畢,你被派去優化這個報表,目的只有一個:最小化執行時間。那麼你除了應用其它優化技巧外,還能夠採起如下手段:

  1)使用反範式化結構建立一個歷史表,並對銷售數據創建合適的索引;

  2)在SQL Server上建立一個按期執行的操做,每隔24小時運行一次,在半夜往歷史表中填充數據;

  3)修改報表代碼,從歷史表獲取數據。

  建立按期執行的操做

  按照下面的步驟在SQL Server中建立一個按期執行的操做,按期從事務表中提取數據填充到歷史表中。

  1)首先確保SQL Server代理服務處於運行狀態;

  2)在SQL Server配置管理器中展開SQL Server代理節點,在「做業」節點上建立一個新做業,在「常規」標籤頁中,輸入做業名稱和描述文字;

  3)在「步驟」標籤頁中,點擊「新建」按鈕建立一個新的做業步驟,輸入名字和TSQL代碼,最後保存;

  4)切換到「調度」標籤頁,點擊「新建」按鈕建立一個新調度計劃;

  5)最後保存調度計劃。

  在數據插入和更新中提早執行耗時的計算,簡化查詢

  大多數狀況下,你會看到你的應用程序是一個接一個地執行數據插入或更新操做,一次只涉及到一條記錄,但數據檢索操做可能同時涉及到多條記錄。

  若是你的查詢中包括一個複雜的計算操做,毫無疑問這將致使總體的查詢性能降低,你能夠考慮下面的解決辦法:

  1)在表中建立額外的一列,包含計算的值;

  2)爲插入和更新事件建立一個觸發器,使用相同的計算邏輯計算值,計算完成後更新到新建的列;

  3)使用新建立的列替換查詢中的計算邏輯。

  實施完上述步驟後,插入和更新操做可能會更慢一點,由於每次插入和更新時觸發器都會執行一下,但數據檢索操做會比以前快得多,由於執行查詢時,數據庫引擎不會執行計算操做了。

  小結

  至此,咱們已經應用了索引,重構TSQL,應用高級索引,反範式化,以及歷史表加速數據檢索速度,但性能優化是一個永無終點的過程,最下一篇文章中咱們將會介紹如何診斷數據庫性能問題。

相關文章
相關標籤/搜索