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 子句
聚合函式(SUM、COUNT、AVG、MAX、MIN)
DISTINCT
集合運算子(UNION、INTERSECT、EXCEPT)
視窗函式(RANK、ROW_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 會非常低效,執
行速度也會變得非常慢。
- 聚合函式(AVG、COUNT、SUM、MIN、MAX)
- 集合運算子(UNION、INTERSECT、EXCEPT 等)
參考書籍:圖靈社群的《SQL進價教程》