1. 程式人生 > 其它 >SQL總複習四:SQL語句效能優化

SQL總複習四:SQL語句效能優化

SQL語句效能優化

介紹一些只需調整 SQL 語句就能實現的通用的優化 Tips。

SQL優化基本出發點,即本質

其實不只是資料庫和 SQL,計算機世界裡容易成為效能瓶頸的也是對硬碟,也就是檔案系統的訪問(因此個人計算機還可以通過增加記憶體,或者使用訪問速度更快的硬碟等方法來提升效能)。不管是減少排序還是使用索引,抑或是避免中間表的使用,都是為了減少對硬碟的訪問。


引數是子查詢時,使用 EXISTS 代替 IN

例如:

-- 慢
SELECT *
 FROM Class_A
 WHERE id IN (SELECT id FROM Class_B);
 
 -- 快
SELECT *
 FROM Class_A A
 WHERE EXISTS (SELECT * FROM Class_B B WHERE A.id = B.id);

引數是子查詢時,使用連線代替 IN

-- 使用連線代替 IN
SELECT A.id, A.name
 FROM Class_A A 
 INNER JOIN Class_B B ON A.id = B.id;

避免排序

會進行排序的代表性的運算有下面這些。

GROUP BY 子句

ORDER BY 子句

聚合函式(SUMCOUNTAVGMAXMIN

DISTINCT

集合運算子(UNIONINTERSECTEXCEPT

視窗函式(RANKROW_NUMBER 等)

排序如果只在記憶體中進行,那麼還好;但是如果記憶體不足因而需要在硬碟上排序,那麼伴隨著“呲啦呲啦”的硬碟訪問聲,排序的效能也會急劇惡化(下面的資料可能不太準確……據說硬碟的訪問速度比記憶體的要慢上 100 萬倍)。因此,儘量避免(或減少)無謂的排序是我們的目標。

使用 EXISTS 代替 DISTINCT

因為後者會進行排序,示例,略

在極值函式中使用索引(MAX/MIN)

即max,或min 函式的引數列,在該列上建立索引。

這種方法並不是去掉了排序這一過程,而是優化了排序前的查詢速度,從而減弱排序對整體效能的影響

能寫在 WHERE 子句裡的條件不要寫在 HAVING 子句裡

原因通常有兩個。第

一個是在使用 GROUP BY 子句聚合時會進行排序,如果事先通過 WHERE 子

句篩選出一部分行,就能夠減輕排序的負擔。第二個是在 WHERE 子句的條

件裡可以使用索引。HAVING 子句是針對聚合後生成的檢視進行篩選的,

但是很多時候聚合後的檢視都沒有繼承原表的索引結構。

在 GROUP BY 子句和 ORDER BY 子句中使用索引

一般來說,GROUP BY 子句和 ORDER BY 子句都會進行排序,來對行

進行排列和替換。不過,通過指定帶索引的列作為 GROUP BY 和 ORDER

BY 的列,可以實現高速查詢。特別是,在一些資料庫中,如果操作物件

的列上建立的是唯一索引,那麼排序過程本身都會被省略掉。

使用索引注意事項

使用索引時,條件表示式的左側應該是原始欄位。如果左邊不是原始欄位,則SQL 語句本來是想使用索引,但實際上執行時卻進行了全表掃描。

比如為col_1 建立了索引:

WHERE col_1 * 1.1 > 100; 不如 WHERE col_1 > 100 / 1.1;

同樣的,在查詢條件左側使用函式時,也不能用到索引,比如下面這句:

WHERE SUBSTR(col_1, 1, 1) = 'a';

下面幾種否定形式也不能用到索引,會全表掃描

  • <>
  • !=
  • not in

使用 or 的時候,兩邊的條件中用到的列如果是有索引的,也是達不到使用索引的理想效果的

使用 LIKE 謂詞時,只有前方一致的匹配才能用到索引。避免使用 LIKE 謂詞進行後方一致或中間一致的匹配,如下面示例:

× SELECT * FROM SomeTable WHERE col_1 LIKE '%a'; 
× SELECT * FROM SomeTable WHERE col_1 LIKE '%a%'; 
√ SELECT * FROM SomeTable WHERE col_1 LIKE 'a%';

應避免進行預設的型別轉換

對 char 型別的“col_1”列指定條件的示例

× SELECT * FROM SomeTable WHERE col_1 = 10;
√ SELECT * FROM SomeTable WHERE col_1 = '10'; 
√ SELECT * FROM SomeTable WHERE col_1 = CAST(10, AS CHAR(2));

預設的型別轉換不僅會增加額外的效能開銷,還會導致索引不可用,可以說是有百害而無一利。雖然這樣寫還不至於出錯,但還是不要嫌麻煩,在需要型別轉換時顯式地進行型別轉換吧(別忘了轉換要寫在條件表示式的右邊)

減少中間表

在 SQL 中,子查詢的結果會被看成一張新表,這張新表與原始表一樣,可以通過程式碼進行操作。這種高度的相似性使得 SQL 程式設計具有非常強的靈活性,但是如果不加限制地大量使用中間表,會導致查詢效能下降。

頻繁使用中間表會帶來兩個問題,一是展開資料需要耗費記憶體資源,二是原始表中的索引不容易使用到(特別是聚合時)。因此,儘量減少中間表的使用也是提升效能的一個重要方法。

靈活使用having子句

對聚合結果指定篩選條件時,使用 HAVING 子句是基本原則。

--使用中間表
SELECT *
 FROM (SELECT sale_date, MAX(quantity) AS max_qty
 FROM SalesHistory
 GROUP BY sale_date) TMP 沒用的中間表
 WHERE max_qty >= 10;
 
 --更優的查詢。HAVING 子句和聚合操作是同時執行的,所以比起生成中間表後再執行的 WHERE 子句,效率會更高一些,而且程式碼看起來也更簡潔。
 SELECT sale_date, MAX(quantity) 
 FROM SalesHistory
 GROUP BY sale_date
 HAVING MAX(quantity) >= 10;
 

合理使用檢視

特別是檢視的定義語句中包含以下運算的時候,SQL 會非常低效,執

行速度也會變得非常慢。

  • 聚合函式(AVGCOUNTSUMMINMAX
  • 集合運算子(UNIONINTERSECTEXCEPT 等)

參考書籍:圖靈社群的《SQL進價教程》