SQL查詢性能優化


使用高效的查詢

  1. 使用 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 的話,數據庫不會生成臨時的工作表。
  1. 參數是子查詢時,使用連接代替 IN
    -- 使用連接代替IN
    SELECT A.id, A.name
      FROM Class_A A INNER JOIN Class_B B
    ON A.id = B.id;
    
    • 這種寫法至少能用到一張表的“id”列上的索引。
    • 因為沒有了子查詢,所以數據庫也不會生成中間表。
  2. 避免排序

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

  • GROUP BY 子句
  • ORDER BY 子句
  • 聚合函數(SUMCOUNTAVGMAXMIN
  • DISTINCT
  • 集合運算符(UNIONINTERSECTEXCEPT
  • 窗口函數(RANKROW_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 的列,可以實現高速查詢。

避免索引未被使用

  1. 在索引字段上進行運算

    -- 不會用到索引
    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';
    

    使用索引時,條件表達式的左側應該是原始字段!!!

  2. is null 謂詞

    通常,索引字段是不存在 NULL 的,所以指定 IS NULL 和 IS NOT NULL 的話會使得索引無法使用,進而導致查詢性能低下。

    在 DB2 和 Oracle 中,IS NULL 條件也能使用索引。這也許是因為它們在實現時為 NULL賦了某個具有特殊含義的值。但是,這個特性不是所有數據庫都有的。

  3. 下面這幾種否定形式不能用到索引,會進行全表查詢。

    • <>

    • !=

    • NOT IN

  4. OR的使用

    在 col_1 和 col_2 上分別建立了不同的索引,或者建立了(col_1, col_2 )這樣的聯合索引時,如果使用 OR 連接條件,那么要么用不到索引,要么用到了但是效率比 AND 要差很多。

    SELECT *
      FROM SomeTable
    WHERE col_1 > 100
    OR col_2 = 'abc';
    
  5. 使用聯合索引時,列的順序錯誤

    假設存在這樣順序的一個聯合索引“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 )必須寫在查詢條件的開頭,而且索引中列的順序不能顛倒。有些數據庫里順序顛倒后也能使用索引,但是性能還是比順序正確時差一些。

    如果無法保證查詢條件里列的順序與索引一致,可以考慮將聯合索引拆分為多個索引。

  6. 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%';
    
  7. 進行默認的類型轉換

    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 中,子查詢的結果會被看成一張新表,這張新表與原始表一 樣,可以通過代碼進行操作。

頻繁使用中間表會帶來兩個問題:

  • 一是展開數據需要耗費內存資源。

  • 二是原始表中的索引不容易使用到(特別是聚合時)。

因此,盡量減 少中間表的使用也是提升性能的一個重要方法。

  1. 靈活使用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;
    
  2. 需要對多個字段使用 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);
    
  3. 合理使用視圖

    視圖是非常方便的工具。但是,如果沒有經過深入思考就定義復雜的視圖,可能會帶來巨大的性能問題。特別是視圖的定義語句中包含以下運算的時候,SQL會非常低效,執行速度也會變得非常慢。

    • 聚合函數(AVGCOUNTSUMMINMAX

    • 集合運算符(UNIONNTERSECTEXCEPT* 等)

  4. 先進行連接再進行聚合

    連接和聚合同時使用時,先進行連接操作可以避免產 生中間表。原因是,從集合運算的角度來看,連接做的是“乘法運算”。連接表雙方是一對一、一對多的關系時,連接運算后數據的行 數不會增加。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM