數據庫查詢優化

[b]1 使用SET NOCOUNT ON 選項:[/b]
缺省地,每次執行SQL語句時,一個消息會從服務端發給客戶端以顯示SQL語句影響的行數。這些信息對客戶端來說很少有用。通過關閉這個缺省值,你能減少在服務端和客戶端的網絡流量,幫助全面提升服務器和應用程序的性能。爲了關閉存儲過程級的這個特點,在每個存儲過程的開頭包含“SET NOCOUNT ON”語句。

[b]2 正確使用UNION和UNION ALL:[/b]
許多人沒完全理解UNION和UNION SELECT是怎樣工作的,因此,結果浪費了大量不必要的SQLServer資源。當使用UNION時,它相當於在結果集上執行SELECT DISTINCT。換句話說,UNION將聯合兩個相類似的記錄集,然後搜索重複的記錄並排除。如果這是你的目的,那麼使用UNION是正確的。但如果你使用UNION聯合的兩個記錄集沒有重複記錄,那麼使用UNION會浪費資源,因爲它要尋找重複記錄,即使你確定它們不存在。

所以如果你知道你要聯合的記錄集裏沒有重複,那麼你要使用UNION ALL,而不是UNION。UNION ALL聯合記錄集,但不搜索重複記錄,這樣減少SQLServer資源的使用,從而提升性能。

[b]3 儘量不用SELECT * :[/b]
絕大多數情況下,不要用 * 來代替查詢返回的字段列表,用 * 的好處是代碼量少、就算是表結構或視圖的列發生變化,編寫的查詢SQL語句也不用變,都返回所有的字段。但數據庫服務器在解析時,如果碰到 *,則會先分析表的結構,然後把表的所有字段名再羅列出來。這就增加了分析的時間。

[b]4 慎用SELECT DISTINCT:[/b]
DISTINCT子句僅在特定功能的時候使用,即從記錄集中排除重複記錄的時候。這是因爲DISTINCT子句先獲取結果集然後去重,這樣增加SQLServer有用資源的使用。當然,如果你需要去做,那就只有去做了。

當如果你知道SELECT語句將從不返回重複記錄,那麼使用DISTINCT語句對SQLServer資源不必要的浪費。

[b]5 少用遊標:[/b]
任何一種遊標都會降低SQLServer性能。有些情況不能避免,大多數情況可以避免。所以如果你的應用程序目前正在使用TSQL遊標,看看這些代碼是否能夠重寫以避免它們。如果你需要一行一行的執行操作,考慮下邊這些選項中的一個或多個來代替遊標的使用:

使用臨時表

使用WHILE循環

使用派生表

使用相關子查詢

使用CASE語句

使用多個查詢

上面每一個都能取代遊標並且執行更快。 如果你不能避免使用遊標,至少試着提高它們的速度,找出加速遊標的方法。

[b]6 選擇最有效率的表名順序:[/b]
SQLSERVER的解析器按照從右到左的順序處理FROM子句中的表名,因此FROM子句中寫在最後的表(基礎表driving table)將被最先處理,在FROM子句中包含多個表的情況下,必須選擇記錄條數最少的表作爲基礎表,當SQLSERVER處理多個表時,會運用排序及合併的方式連接它們。首先,掃描第一個表(FROM子句中最後的那個表)並對記錄進行排序;然後掃描第二個表(FROM子句中最後第二個表);最後將所有從第二個表中檢索出的記錄與第一個表中合適記錄進行合併。

例如: 表 TAB1有 16384 條記錄,表 TAB2 有5條記錄,選擇TAB2作爲基礎表 (最好的方法):

select count(*) from TAB1 a, TAB2 b

選擇TAB1作爲基礎表 (不佳的方法):

select count(*) from TAB2 a, TAB1 b

如果有3個以上的表連接查詢,那就需要選擇交叉表(intersection table)作爲基礎表,交叉表是指那個被其他表所引用的表。

[b]7 使用表的別名(Alias):[/b]
當在SQL語句中連接多個表時,請使用表的別名並把別名前綴於每個Column上,這樣可以減少解析的時間並減少那些由Column歧義引起的語法錯誤。

[b]8 SARG你的WHERE條件:[/b]
ARGE來源於"Search Argument"(搜索參數)的首字母拼成的"SARG",它是指WHERE子句裏,列和常量的比較。如果WHERE子句是sargable(可SARG的),這意味着它能利用索引加速查詢的完成。如果WHERE子句不是可SARG的,這意味着WHERE子句不能利用索引(或至少部分不能利用),執行的是全表或索引掃描,這會引起查詢的性能下降。

在WHERE子句裏不可SARG的搜索條件如"IS NULL", "<>", "!=", "!>", "!<", "NOT", "NOT EXISTS", "NOT IN", "NOT LIKE"和"LIKE '%500'",通常(但不總是)會阻止查詢優化器使用索引執行搜索。另外在列上使用包括函數的表達式、兩邊都使用相同列的表達式、或和一個列(不是常量)比較的表達式,都是不可SARG的。

並不是每一個不可SARG的WHERE子句都註定要全表掃描。如果WHERE子句包括兩個可SARG和一個不可SARG的子句,那麼至少可SARG的子句能使用索引(如果存在的話)幫助快速訪問數據。

大多數情況下,如果表上有包括查詢裏所有SELECT、JOIN、WHERE子句用到的列的覆蓋索引,那麼覆蓋索引能夠代替全表掃描去返回查詢的數據,即使它有不可SARG的WHERE子句。但記住覆蓋索引尤其自身的缺陷,如此經常產生寬索引會增加讀磁盤I/O。某些情況下,可以把不可SARG的WHERE子句重寫成可SARG的子句。例如:

WHERE SUBSTRING(firstname,1,1) = 'm'

可以寫成:

WHERE firstname like 'm%'

這兩個WHERE子句有相同的結果,但第一個是不可SARG的(因爲使用了函數)將運行得慢些,而第二個是可SARG的,將運行得快些。

如果你不知道特定的WHERE子句是不是可SARG的,在查詢分析器裏檢查查詢執行計劃。這樣做,你能很快的知道查詢是使用了索引還是全表掃描來返回的數據。仔細分析,許多不可SARG的查詢能寫成可SARG的查詢。下面分幾點講解WHERE條件的SARG。

[b]8.1 WHERE子句中的連接順序[/b]
SQLSERVER採用自下而上的順序解析WHERE子句,根據這個原理,表之間的連接必須寫在其他WHERE條件之前,那些可以過濾掉最大數量記錄的條件必須寫在WHERE子句的末尾。例如:

(低效)

SELECT * FROM EMP E

WHERE SAL > 50000

AND JOB = ‘MANAGER’

AND 25 < (SELECT COUNT(*) FROM EMP WHERE MGR=E.EMPNO)


(高效)

SELECT * FROM EMP E

WHERE 25 < (SELECT COUNT(*) FROM EMP WHERE MGR=E.EMPNO)

AND SAL > 50000

AND JOB = ‘MANAGER’

[b]8.2 避免困難的正規表達式:[/b]
MATCHES和LIKE關鍵字支持通配符匹配,技術上叫正規表達式。但這種匹配特別耗費時間。例如:

SELECT * FROM customer WHERE zipcode LIKE "98_ _ _"

即使在zipcode字段上建立了索引,在這種情況下也還是採用順序掃描的方式。如果把語句改爲SELECT * FROM customer WHERE zipcode >="98000",在執行查詢時就會利用索引來查詢,顯然會大大提高速度。

另外,還要避免非開始的子串。例如語句:

SELECT * FROM customer WHERE zipcode[2,3] >"80"

在where子句中採用了非開始子串,因而這個語句也不會使用索引。

[b]8.3 避免對大型錶行數據的順序存取:[/b]
在嵌套查詢中,對錶的順序存取對查詢效率可能產生致命的影響。比如採用順序存取策略,一個嵌套3層的查詢,如果每層都查詢1000行,那麼這個查詢就要查詢10億行數據。避免這種情況的主要方法就是對連接的列進行索引。例如,兩個表:學生表(學號、姓名、年齡……)和選課表(學號、課程號、成績)。如果兩個表要做連接,就要在“學號”這個連接字段上建立索引。

還可以使用並集來避免順序存取。儘管在所有的檢查列上都有索引,但某些形式的where子句強迫優化器使用順序存取。下面的查詢將強迫對orders表執行順序操作:

SELECT * FROM orders WHERE (customer_num=104 AND order_num>1001) OR order_num=1008

雖然在customer_num和order_num上建有索引,但是在上面的語句中優化器還是使用順序存取路徑掃描整個表。因爲這個語句要檢索的是分離的行的集合,所以應該改爲如下語句:

SELECT * FROM orders WHERE customer_num=104 AND order_num>1001

UNION ALL

SELECT * FROM orders WHERE order_num=1008

這樣就能利用索引路徑處理查詢。

[b]8.4 EXISTS和IN的使用:[/b]
在許多基於基礎表的查詢中,爲了滿足一個條件,往往需要對另一個表進行聯接。   在這種情況下,使用EXISTS(或NOT EXISTS)通常將提高查詢的效率。在子查詢中,NOT IN子句將執行一個內部的排序和合並。無論在哪種情況下,NOT IN都是最低效的,因爲它對子查詢中的表執行了一個全表遍歷。爲了避免使用NOT IN,我們可以把它改寫成外連接(Outer Joins)或NOT EXISTS。

[b]8.5 避免在索引列上使用IS NULL和IS NOT NULL:[/b]
避免在索引中使用任何可以爲空的列,SQLSERVER將無法使用該索引。對於單列索引,如果列包含空值,索引中將不存在此記錄;對於複合索引,如果每個列都爲空,索引中同樣不存在此記錄。如果至少有一個列不爲空,則記錄存在於索引中。  

  如果唯一性索引建立在表的A列和B列上,並且表中存在一條記錄的A,B值爲(123,null),SQLSERVER將不接受下一條具有相同A,B值(123,null)的記錄插入。  

  如果所有的索引列都爲空,SQLSERVER將認爲整個鍵值爲空,而空不可能等於空,因此你可以插入1000條具有相同鍵值的記錄,當然它們都是空!因爲空值不存在於索引列中,所以WHERE子句中對索引列進行空值比較將使SQLSERVER停用該索引。下面的代碼將會很低效(索引失效):

SELECT … FROM DEPARTMENT WHERE DEPT_CODE IS NOT NULL

[b]8.6 避免在索引列上使用計算:[/b]
WHERE子句中,如果索引列是函數的一部分,優化器將不使用索引而使用全表掃描。   例如下面的語句低效 :

SELECT … FROM DEPT WHERE SAL * 12 > 25000

而下面的語句將是高效的:

SELECT … FROM DEPT WHERE SAL > 25000/12

請務必注意,查詢中不要對索引列進行處理,如:TRIM,substring,convert等等操作。

[b]8.7 用WHERE子句替換HAVING子句:[/b]
避免使用HAVING子句,HAVING只會在檢索出所有記錄之後纔對結果集進行過濾,這個處理需要排序、統計等操作。如果能通過WHERE子句限制記錄的數目,那就能減少這方面的開銷。

[b]9 避免或簡化排序:[/b]
應當簡化或避免對大型表進行重複的排序。當能夠利用索引自動以適當的次序產生輸出時,優化器就避免了排序的步驟。以下是一些影響因素:

* 索引中不包括一個或幾個待排序的列;

* group by或order by子句中列的次序與索引的次序不一樣;

* 排序的列來自不同的表。

爲了避免不必要的排序,就要正確地增建索引,合理地合併數據庫表(儘管有時可能影響表的規範化,但相對於效率的提高是值得的)。如果排序不可避免,那麼應當試圖簡化它,如縮小排序的列的範圍等。

[b]10 臨時表的使用:[/b]
臨時表有很多特殊的用途,象用來替代遊標,不過它們仍能引起性能問題,如果這個問題能消除,SQLServer將執行得更快。在永久表和臨時表的數據行相同的條件下,使用臨時表沒有永久錶快。但有時還必須得使用臨時表,如先從存儲大量數據的永久表中提取符全條件的存放到臨時表,然後在臨時表上執行操作。如果是直接在存儲大量數據的永久表上執行操作(如:統計、循環等),其性能將大打折扣。所以,使不使用臨時表,何時使用臨時表,需要具體情況決定。

[b]11 是否使用視圖:[/b]
視圖最大的用途是處理安全相關的問題,而不是一些懶惰的開發人員用來存儲經常使用的查詢的方法。例如,如果你需要允許用戶訪問特定SQLServer的數據,那麼你也許可以考慮爲用戶(或組)創建一個視圖,然後給用戶訪問視圖而不是基表的權限。另一方面,在應用程序裏,從視圖選擇數據沒有好的理由,相反,繞過視圖直接從需要的表裏獲取數據。原因是許多視圖(當然不是全部)返回比SELECT語句所需更多的數據,增加不必要的開銷。

例如,假定有一個視圖從兩個連接表裏返回10列。你想要從視圖裏使用SELECT語句返回其中7列。實際上發生的情況是基於視圖的查詢先運行,返回數據,然後你的查詢針對這些數據運行。既然你僅需要7列,而不是視圖返回的10列,更多不必要的數據被返回。浪費SQLServer的資源。

長久以來,大家在爭論是查詢視圖速度快還是直接查詢快,本人也不敢輕易下結論,因此作了多次試驗,其結果是:基於視圖查詢,性能確實不會比直接寫查詢語句快,對於簡單的查詢,最多是在同一水平上。

當然,上面的測試是在沒有爲視圖創建索引的情況下,SQLServer2000以上可以爲視圖創建索引,視圖索引與表的索引在作用方式上非常相似。與表一樣,視圖可以有一個集簇索引(clustered index)和多個非集簇索引。創建視圖索引後能夠提高視圖的性能。

如果視圖不包含索引,則數據庫中不保存視圖返回的結果集。有的時候,我們可能要創建涉及大量記錄或必須進行復雜計算的視圖,比如要進行聚合分組處理或多重連接操作。如果每次引用這些視圖的時候讓sql server重新生成結果集,數據庫開銷將非常大。

[b]12 讓事務儘可能的短:[/b]
保持TSQL事務儘可能的短。這會幫助減少鎖(所有類型的鎖)的數量,有助於全面提升SQLServer的性能。如果有經驗,你也許要將長事務分成更小的事務組。

[b]13 用存儲過程代替直接寫查詢語句:[/b]
存儲過程爲開發人員提供了很多好處,包括:

* 減少網絡流量和響應時間,提升應用程序性能。例如,通過網絡發送一個存儲過程調用,而不是發送500行的TSQL將更快,資源使用更少。當每次執行SQL時,都會執行解析SQL語句、估算索引的利用率、綁定變量、讀數據塊等等工作。

* 存儲過程執行計劃能夠重用,駐留在SQLServer內存的緩存裏,減少服務器開銷。

* 客戶端執行請求更有效率。例如,如果應用程序需要插入大量的二進制值到一個image數據列而不使用存儲過程,它必須轉化二進制爲字符串(大小會增加一倍),然後發送給SQLServer。當SQLServer接收到後,它必須把字符串值轉回二進制格式。大量的浪費開銷。存儲過程能消除這個問題通過將應用程序傳給SQLServer的二進制格式作爲參數,從而減少開銷提升性能。

* 存儲過程幫助提供代碼重用。雖然這些不直接提升應用程序的性能,通過減少代碼量和減少調試時間來提升開發人員的效率。

* 存儲過程能封裝邏輯。你能夠改變存儲過程代碼而不影響客戶端(假定你保持參數相同也不移除任何結果集的列)。這節約開發人員的時間。

* 存儲過程爲你的數據提供更好的安全性。如果你僅使用存儲過程,你可以移除直接對錶的SELECT、INSERT、UPDATE和DELETE權限從而強迫開發人員使用存儲過程訪問數據。這會節約DBA的時間。

* 作爲首要的常規,所有的TSQL代碼都應該通過存儲過程調用。

[b]13.1 存儲過程名不要以 sp_ 開頭:[/b]
對這一準則,可能很多人會感覺納悶,是的,我開始也納悶過。如果創建的存儲過程不是運行在Master數據庫裏,不要使用以sp_爲前綴的名稱。這個特別的前綴是爲系統存儲過程保留的。儘管使用這個前綴不會禁止用戶定義的存儲過程的運行,但會稍微降低一些執行效率。這是因爲SQLServer在執行以sp_爲前綴的任何一個存儲過程時缺省地首先試圖在Master數據庫裏尋找,儘管那兒沒有,這就浪費了尋找存儲過程的時間。如果SQLServer在Master數據庫裏不能找到存儲過程,那麼接下來會將存儲過程的擁有者作爲DBO去解析。如果存儲過程在目前的數據庫裏,那麼它會執行。爲了避免不必要的延遲,不要用前綴爲sp_命名你的任何一個存儲過程。

[b]13.2 存儲過程的擁有者要相同:[/b]
爲了最好的性能,同一個存儲過程裏調用的所有對象的擁有者都應該相同,DBO更適宜。如果不是那樣,即對象名相同而擁有者不同,那麼SQLServer必須執行名稱判斷。當發生這樣的情形時,SQLServer不能使用存儲過程裏在內存裏的執行計劃,相反,它必須重新編譯存儲過程,從而影響性能。當從應用程序裏調用存儲過程時,使用分隔符名稱來調用也是重要的。如:

EXEC dbo.myProcedure

代替:

EXEC myProcedure

這樣做有兩個原因,其中一個和性能有關。首先,使用完全有分隔符的名稱有助於消除那些和你要運行的存儲過程有潛在的混淆,有助於禁止BUG和潛在的問題。但更重要的是,這樣做SQLServer能更直接的訪問存儲過程執行計劃,而不是輪流訪問,從而加速了存儲過程的性能。當然性能提升很小,但如果你的服務器每小時要運行成千上萬或更多的存儲過程,這些節約的小段時間加起來就很可觀了。

[b]14 完整性使用下的約束和觸發器:[/b]
數據庫裏不要執行多餘的完整性特點。例如,如果你正使用主鍵和外鍵約束來強迫引用完整性,則不要添加觸發器來實現相同的功能而增加不必要的開銷。同樣既使用約束又使用默認值或既使用約束又使用規則也會執行多餘的工作。

[b]15 在SQL中捕捉異常:[/b]
這一條準則應該不能算是優化方面的,只是編寫要求。現在SQLServer2005中,新增了BEGIN TRY…END TRY和 BEGIN CATCH…END CATCH二個成對語句,用於捕捉運行時出現的異常。在Oracle中,可用 BEGIN…EXCEPTION…END 語句捕捉異常。

把SQL代碼塊中加入捕捉異常的語句內,有二個好處:一是可以在SQL語句內部得到異常並作錯誤處理,如在錯誤代碼塊內返回自定義錯誤信息、ROLBACK等。這樣可減少應用程序捕捉異常帶來的資源開銷;另外一個好處就是可以防止死鎖情況的發生,當出現死鎖時,SQLServer2005會拋出異常,我們就可捕捉到。


下面列出一些索引的概念,有助於設計表結構和編寫SQL語句:

按照存儲規則來分:

* 聚集索引:該索引中鍵值的邏輯順序決定了表中相應行的物理順序。因此一個表只能包含一個聚集索引,但該索引可以包含多個列(組合索引)。檢索效率比普通索引高,但對數據新增/修改/刪除的影響比較大。

* 非聚集索引:與聚集索引相對,不影響表中的數據存儲順序,檢索效率比聚集索引低,對數據新增/修改/刪除的影響很少。

按照維護與管理的角度來分:

* 唯一索引:惟一索引可以確保索引列不包含重複的值,可以用多個列,但是索引可以確保索引列中每個值組合都是唯一的。

* 主鍵索引:在數據庫關係圖中爲表定義一個主鍵將自動創建主鍵索引,主鍵索引是唯一索引的特殊類型。主鍵索引要求主鍵中的每個值是唯一的。當在查詢中使用主鍵索引時,它還允許快速訪問數據。

* 普通索引:由關鍵字KEY或INDEX定義的索引,唯一任務是加快對數據的訪問速度。因此,應該只爲那些最經常出現在查詢條件或排序條件中的數據列創建索引。只要有可能,就應該選擇一個數據最整齊、最緊湊的數據列(如整數類型的數據列)來創建索引。允許有重複的列存在。

* 複合索引:如果在兩上以上的列上創建的索引,則稱爲複合索引。
發佈了42 篇原創文章 · 獲贊 1 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章