使用高效的查詢
-
使用 EXISTS 代替 IN
-- 查詢A表中同時存在B表的數據
-- 慢
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);
EXISTS更快的原因:
- 如果連接列(id )上建立了索引,那么查詢 Class_B 時不用查 實際的表,只需查索引就可以了。
- 如果使用 EXISTS ,那么只要查到一行數據滿足條件就會終止 查詢,不用像使用 IN 時一樣掃描全表。在這一點上 NOT EXISTS 也一樣。
- 當 IN 的參數是子查詢時,數據庫首先會執行子查詢,然后將結果存 儲在一張臨時的工作表里(內聯視圖),然后掃描整個視圖,很多情況下很消耗資源。使用 EXISTS 的話,數據庫不會生成臨時的工作表。
-
參數是子查詢時,使用連接代替 IN
-- 使用連接代替IN SELECT A.id, A.name FROM Class_A A INNER JOIN Class_B B ON A.id = B.id;
- 這種寫法至少能用到一張表的“id”列上的索引。
- 因為沒有了子查詢,所以數據庫也不會生成中間表。
-
避免排序
會進行排序的代表性的運算有下面這些:
- GROUP BY 子句
- ORDER BY 子句
- 聚合函數(SUM 、COUNT 、AVG 、MAX 、MIN )
- DISTINCT
- 集合運算符(UNION 、INTERSECT 、EXCEPT )
- 窗口函數(RANK 、ROW_NUMBER 等)
排序如果只在內存中進行,那么還好;但是如果內存不足因而需要在 硬盤上排序,那么伴隨着“呲啦呲啦”的硬盤訪問聲,排序的性能也會 急劇惡化
靈活使用集合運算符的 ALL 可選項
SQL 中有 UNION 、INTERSECT 、EXCEPT(MINUS) 三個集合運算符。在默認的使用方式下,這些運算符會為了排除掉重復數據而進行排序。
SELECT * FROM Class_A
UNION
SELECT * FROM Class_B;
如果不在乎結果中是否有重復數據,或者事先知道不會有重復數據,請使用 UNION ALL 代替 UNION 。這樣就不會進行排序了。
使用 EXISTS 代替 DISTINCT
為了排除重復數據,DISTINCT 也會進行排序。如果需要對兩張表的連接結果進行去重,可以考慮使用 EXISTS 代替 DISTINCT ,以避免排序。
-- 使用DISTINCT去重
SELECT DISTINCT I.item_no
FROM Items I INNER JOIN SalesHistory SH
ON I. item_no = SH. item_no;
-- 使用exists去重,避免排序
SELECT item_no
FROM Items I
WHERE EXISTS (SELECT *
FROM SalesHistory SH
WHERE I.item_no = SH.item_no);
在極值函數中使用索引(MAX/MIN)
SQL 語言里有 MAX 和 MIN 兩個極值函數。使用這兩個函數時都會進行排序。但是如果參數字段上建有索引,則
只需要掃描索引,不需要掃描整張表。
能寫在 WHERE 子句里的條件不要寫在 HAVING 子句里
-- 聚合后使用HAVING 子句過濾
SELECT sale_date, SUM(quantity)
FROM SalesHistory
GROUP BY sale_date
HAVING sale_date = '2007-10-01';
-- 聚合前使用WHERE 子句過濾
SELECT sale_date, SUM(quantity)
FROM SalesHistory
WHERE sale_date = '2007-10-01'
GROUP BY sale_date;
但是從性能上來看,第二條語句寫法效率更高。原因通常有兩個。第一個是在使用 GROUP BY 子句聚合時會進行排序,如果事先通過WHERE 子句篩選出一部分行,就能夠減輕排序的負擔。第二個是在WHERE 子句的條件里可以使用索引。HAVING 子句是針對聚合后生成的視圖進行篩選的,但是很多時候聚合后的視圖都沒有繼承原表的索引結構 。
在 GROUP BY 子句和 ORDER BY 子句中使用索引
一般來說,GROUP BY 子句和 ORDER BY 子句都會進行排序,來對行 進行排列和替換。不過,通過指定帶索引的列作為 GROUP BY 和 ORDER BY 的列,可以實現高速查詢。
避免索引未被使用
-
在索引字段上進行運算
-- 不會用到索引 SELECT * FROM SomeTable WHERE col_1 * 1.1 > 100; --把運算的表達式放到查詢條件的右側,就能用到索引了 SELECT * FROM SomeTable WHERE col_1 > 100 / 1.1; --在查詢條件的左側使用函數時,也不能用到索引。 SELECT * FROM SomeTable WHERE SUBSTR(col_1, 1, 1) = 'a';
使用索引時,條件表達式的左側應該是原始字段!!!
-
is null 謂詞
通常,索引字段是不存在 NULL 的,所以指定 IS NULL 和 IS NOT NULL 的話會使得索引無法使用,進而導致查詢性能低下。
在 DB2 和 Oracle 中,IS NULL 條件也能使用索引。這也許是因為它們在實現時為 NULL賦了某個具有特殊含義的值。但是,這個特性不是所有數據庫都有的。
-
下面這幾種否定形式不能用到索引,會進行全表查詢。
-
<>
-
!=
-
NOT IN
-
-
OR的使用
在 col_1 和 col_2 上分別建立了不同的索引,或者建立了(col_1, col_2 )這樣的聯合索引時,如果使用 OR 連接條件,那么要么用不到索引,要么用到了但是效率比 AND 要差很多。
SELECT * FROM SomeTable WHERE col_1 > 100 OR col_2 = 'abc';
-
使用聯合索引時,列的順序錯誤
假設存在這樣順序的一個聯合索引“col_1, col_2, col_3 ”這時,指定條件的順序就很重要。
○ SELECT * FROM SomeTable WHERE col_1 = 10 AND col_2 = 100 AND col_3 = 500; ○ SELECT * FROM SomeTable WHERE col_1 = 10 AND col_2 = 100 ; × SELECT * FROM SomeTable WHERE col_1 = 10 AND col_3 = 500 ; × SELECT * FROM SomeTable WHERE col_2 = 100 AND col_3 = 500 ; × SELECT * FROM SomeTable WHERE col_2 = 100 AND col_1 = 10 ;
聯合索引中的第一列(col_1 )必須寫在查詢條件的開頭,而且索引中列的順序不能顛倒。有些數據庫里順序顛倒后也能使用索引,但是性能還是比順序正確時差一些。
如果無法保證查詢條件里列的順序與索引一致,可以考慮將聯合索引拆分為多個索引。
-
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 中,子查詢的結果會被看成一張新表,這張新表與原始表一 樣,可以通過代碼進行操作。
頻繁使用中間表會帶來兩個問題:
-
一是展開數據需要耗費內存資源。
-
二是原始表中的索引不容易使用到(特別是聚合時)。
因此,盡量減 少中間表的使用也是提升性能的一個重要方法。
-
靈活使用having子句
對聚合結果指定篩選條件時,使用 HAVING 子句是基本原則。
不習慣使用 HAVING 子句的可能會傾向於生成一 張中間表
SELECT * FROM (SELECT sale_date, MAX(quantity) AS max_qty FROM SalesHistory GROUP BY sale_date) TMP ←----- 沒用的中間表 WHERE max_qty >= 10; --對聚合結果指定篩選條件時不需要專門生成中間表 SELECT sale_date, MAX(quantity) FROM SalesHistory GROUP BY sale_date HAVING MAX(quantity) >= 10;
-
需要對多個字段使用 IN 謂詞時,將它們匯總到一處
-- 這段代碼中用到了兩個子查詢 SELECT id, state, city FROM Addresses1 A1 WHERE state IN (SELECT state FROM Addresses2 A2 WHERE A1.id = A2.id) AND city IN (SELECT city FROM Addresses2 A2 WHERE A1.id = A2.id); --把字段連接在一起,子查詢不用考慮關聯性,而且只執行一次就可以 SELECT * FROM Addresses1 A1 WHERE id || state || city IN (SELECT id || state|| city FROM Addresses2 A2); -- 如果所用的數據庫實現了行與行的比較 SELECT * FROM Addresses1 A1 WHERE (id, state, city) IN (SELECT id, state, city FROM Addresses2 A2);
-
合理使用視圖
視圖是非常方便的工具。但是,如果沒有經過深入思考就定義復雜的視圖,可能會帶來巨大的性能問題。特別是視圖的定義語句中包含以下運算的時候,SQL會非常低效,執行速度也會變得非常慢。
-
聚合函數(AVG 、COUNT 、SUM 、MIN 、MAX )
-
集合運算符(UNION 、NTERSECT 、EXCEPT* 等)
-
-
先進行連接再進行聚合
連接和聚合同時使用時,先進行連接操作可以避免產 生中間表。原因是,從集合運算的角度來看,連接做的是“乘法運算”。連接表雙方是一對一、一對多的關系時,連接運算后數據的行 數不會增加。