簡介
對於寫出實現功能的SQL語句和既能實現功能又能保證性能的SQL語句的差別是巨大的。很多時候開發人員僅僅是把精力放在實現所需的功能上,而忽略了其所寫代碼的性能和對SQL Server實例所產生的影響(也就是IO,CPU,內存方面的消耗).這甚至有可能使整個SQL Server實例跪了。本文旨在提供一些簡單的步驟來幫助你優化SQL語句。
市面上已經有很多關於如何優化SQL Server性能的書籍和白皮書。所以本文並不打算達到那種深度和廣度,而僅僅是為開發人員提供一個快速檢測的列表來找到SQL語句中導致瓶頸產生的部分。
在開始解決性能問題之前,合適的診斷工具是必須的。除去眾所周知的SSMS和SQL Profiler,SQL Server 2008還帶有眾多DMV來提供關鍵信息。本篇文章中,我將使用SSMS和一些DMV來找到SQL的瓶頸
那么,我們從哪開始
我的第一步是查看執行計划。這一步既可以通過SMSS也可以通過SQL Profiler實現,為了簡便起見,我將在SMSS中獲取執行計划。
1) 檢查你是否忽略掉了某些表的連接的條件,從而導致了笛卡爾積(Cross)連接(Join)。比如,在生產系統中有兩個表,每個表中有1000行數據。這其中絕大多數數據並不需要返回,如果你在這兩個表上應用了Cross Join,返回的結果將會是100萬行的結果集!返回如此數量的數據包括將所有數據從物理存儲介質中讀取出來,因而占用了IO。然后這些數據將會被導入內存,也就是SQL Server的緩沖區。這會將緩沖區內的其它頁Flush出去。
2)查看你是否忽略了某些Where子句,缺少Where子句會導致返回額外不需要的行。這產生的影響和步驟一所產生的影響是一樣的。
3)查看統計信息是否是自動創建和自動更新的,你可以在數據庫的屬性里看到這些選項
在默認條件下創建一個新數據庫,Auto Create Statistics和Auto Update Statistics選項是開啟的,統計信息是用於幫助查詢優化器生成最佳執行計划的。這份白皮書對於解釋統計信息的重要性以及對於執行計划的作用解釋的非常到位。上面那些設置可以通過右鍵數據庫,選擇屬性,在“選項”中找到。
4)檢查統計信息是否已經過期,雖然統計信息是自動創建的,但是更新統計信息從而反映出數據的變化也同樣重要。在一個大表中,有時候雖然Auto Update Statistics 選項已經開始,但統計信息依然無法反映出數據的分布情況。默認情況下,統計信息的更新是基於抽取表中的隨機信息作為樣本產生的。如果數據是按順序存儲的,那么很有可能數據樣本並沒有反映出表中的數據情況。因此,推薦在頻繁更新的表中,統計信息使用Full Scan選項來定期更新。這種更新可以放到數據庫閑時來做。
DBCC SHOW_STATISTICS命令可以用於查看上次統計信息的更新時間,行數以及樣本行數.在這個例子中,我們可以看到Person.Address表上的AK_Address_rowguid索引的有關信息:
USE AdventureWorks;
GO
DBCC SHOW_STATISTICS ("Person.Address", AK_Address_rowguid);
GO
下面是輸出結果,請注意Updated,Rows,Rows Sampled這三個列
如果你認為統計信息已經過期,則可以使用sp_updatestats這個存儲過程來更新當前數據庫中的所有統計信息:
或者使用FULLSCAN選項,則關於表Person.Address上的所有統計信息將會被更新:
UPDATE STATISTICS Person.Address WITH FULLSCAN
5)查看執行計划是否出現任何表或者索引的掃描(譯者注:不是查找),在大多數情況下(這里假設統計信息是最新的),這意味着索引的缺失。下面幾個DMV對於查找缺失索引很有幫助:
i) sys.dm_db_missing_index_details
接下來的幾個語句使用了上面的DMV,按照索引缺失對於性能的影響,展現出信息:
SELECT avg_total_user_cost,avg_user_impact,user_seeks, user_scans, ID.equality_columns,ID.inequality_columns,ID.included_columns,ID.statement FROM sys.dm_db_missing_index_group_stats GS LEFT OUTER JOIN sys.dm_db_missing_index_groups IG On (IG.index_group_handle = GS.group_handle) LEFT OUTER JOIN sys.dm_db_missing_index_details ID On (ID.index_handle = IG.index_handle) ORDER BY avg_total_user_cost * avg_user_impact * (user_seeks + user_scans)DESC
你也可以使用數據引擎優化顧問來找出缺失的索引以及需要創建哪些索引來提高性能。
6)查看是否有書簽查找,同樣,在執行計划中找到書簽查找十分容易,書簽查找並不能完全避免,但是使用覆蓋索引可以大大減少書簽查找。
7)查看排序操作,如果在執行計划中排序操作占去了很大一部分百分比,我會考慮以下幾種方案:
- 按照所排序的列創建聚集索引,但這種方式一直存在爭議。因為最佳實踐是使用唯一列或者Int類型的列作為主鍵,然后讓SQL Server在主鍵上創建聚集索引。但是在特定情況下使用排序列創建聚集索引也是可以的
- 創建一個索引視圖,在索引視圖上按照排序列創建聚集索引
- 創建一個排序列的非聚集索引,把其他需要返回的列INCLUDE進去
在我的另一篇文章中,我將會詳細闡述選擇最佳方案的方法。
8)查看加在表上的鎖,如果所查的表由於一個DML語句導致上鎖,則查詢引擎需要花一些時間等待鎖的釋放。下面是一些解決鎖問題的方法:
- 讓事務盡可能的短
- 查看數據庫隔離等級,降低隔離等級以增加並發
- 在Select語句中使用表提示,比如READUNCOMMITTED 或 READPAST.雖然這兩個表提示都會增加並發,但是ReadUnCommited可能會帶來臟讀的問題,而READPAST會只返回部分結果集
9)查看是否有索引碎片,索引碎片可以使用sys.dm_db_index_physical_statsDMV輕松查看,如果索引碎片已經大於30%,則推薦索引重建.而索引碎片小於30%時,推薦使用索引整理。索引碎片因為使查詢需要讀取更多的列從而增加了IO,而更多的頁意味着占用更多的緩沖區,因此還會形成內存壓力。
如下語句根據索引碎片的百分比查看所有索引:
Declare @db SysName; Set @db = '<DB NAME>'; SELECT CAST(OBJECT_NAME(S.Object_ID, DB_ID(@db)) AS VARCHAR(20)) AS 'Table Name', CAST(index_type_desc AS VARCHAR(20)) AS 'Index Type', I.Name As 'Index Name', avg_fragmentation_in_percent As 'Avg % Fragmentation', record_count As 'RecordCount', page_count As 'Pages Allocated', avg_page_space_used_in_percent As 'Avg % Page Space Used' FROM sys.dm_db_index_physical_stats (DB_ID(@db),NULL,NULL,NULL,'DETAILED' ) S LEFT OUTER JOIN sys.indexes I On (I.Object_ID = S.Object_ID and I.Index_ID = S.Index_ID) AND S.INDEX_ID > 0 ORDER BY avg_fragmentation_in_percent DESC
下面語句可以重建指定表的所有索引:
下面語句可以重建指定索引:
當然,我們也可以整理索引,下面語句整理指定表上的所有索引:
下面語句指定特定的索引進行整理:
在重建或整理完索引之后,重新運行上面的語句來查看索引碎片的情況。
總結
上面的9個步驟並不是優化一個SQL語句必須的,盡管如此,你還是需要盡快找到是哪個步驟導致查詢性能的瓶頸從而解決性能問題。就像文中開篇所說,性能的問題往往是由於更深層次的原因,比如CPU或內存壓力,IO的瓶頸(這個列表會很長….),因此,更多的研究和閱讀是解決性能問題所必須的。
----------------------------------------
原文鏈接:http://www.sqlservercentral.com/articles/Performance+Tuning/70647/