標籤: 查詢

  • 2025 年 SQL 查詢優化:提升資料庫效能的 7 個簡單技巧

    嘿!在這篇文章中,我將分享一些簡單的方法來加速你的 SQL 查詢並使其更有效率。我們將看一些實際的例子(主要聚焦於PostgreSQL,儘管這些概念也適用於其他 SQL 資料庫)。到最後,你將能夠優化查詢以獲得更好的性能和更快的回應時間。

    SQL 優化:因為你的查詢值得快速通道。

    1. 將 IN 替換為在虛擬表上的 JOIN

    問題:
    IN子句中擁有大量值可能導致對每一行進行順序檢查,從而過載 CPU。

    解決方案:
    使用虛擬表(帶有VALUES)並將其JOIN到主表。這通常能讓數據庫更有效地利用索引。

    範例

    -- BEFORE:
    EXPLAIN
    SELECT order_id, city
      FROM orders
     WHERE city IN ('Berlin', 'Paris', 'Rome');
    
    -- AFTER:
    EXPLAIN
    SELECT o.order_id, o.city
      FROM orders AS o
      JOIN (
            VALUES ('Berlin'), ('Paris'), ('Rome')
           ) AS v(city_name)
        ON o.city = v.city_name;

    為什麼它更快:
    通過將值列表視為表格,PostgreSQL 有時可以創建比使用冗長IN子句更優化的執行計劃。

    2. 使用 ANY(ARRAY [ ]) 而不是 IN(僅限 PostgreSQL)

    問題:
    類似於第一個案例,一個大型的IN列表可能會拖慢查詢速度,因為每個可能的匹配都必須逐一檢查。

    解決方案:
    試試 PostgreSQL 專用的語法 = ANY(ARRAY[...]),它可以在找到匹配時立即短路。

    範例

    -- BEFORE:
    EXPLAIN
    SELECT product_id, quantity
      FROM order_items
     WHERE product_id IN (101, 202, 303, 404);
    
    -- AFTER:
    EXPLAIN
    SELECT product_id, quantity
      FROM order_items
     WHERE product_id = ANY(ARRAY[101, 202, 303, 404]);

    為什麼它更快:
    ANY 一旦遇到匹配項即可停止檢查,從而可能減少比較的次數。

    3. 使用 JOIN 代替相關子查詢

    問題:
    相關子查詢會為外部查詢中的每一行重複執行,導致對同一數據進行多次掃描。

    解決方案:
    盡可能將相關子查詢替換為常規的JOIN或非相關子查詢。

    範例

    -- BEFORE:
    EXPLAIN
    SELECT c.customer_id, c.name
      FROM customers AS c
     WHERE EXISTS (
           SELECT 1
             FROM orders AS o
            WHERE o.customer_id = c.customer_id
              AND o.amount > 1000
           );
    
    -- AFTER (JOIN):
    EXPLAIN
    SELECT DISTINCT c.customer_id, c.name
      FROM customers AS c
      JOIN orders AS o
        ON c.customer_id = o.customer_id
     WHERE o.amount > 1000;

    為什麼它更快:
    JOIN 允許 PostgreSQL 使用索引並避免多次執行相同的子查詢。

    4. 使用BETWEEN代替日期函數

    問題:
    像 EXTRACT(YEAR FROM order_date) = 2023 這樣的表達式會阻止資料庫使用日期索引。對欄位應用函數會使 PostgreSQL 無法識別索引範圍。

    解決方案:
    使用BETWEEN與明確的日期範圍,以允許直接在order_date欄位上使用索引。

    範例

    -- BEFORE:
    EXPLAIN
    SELECT *
      FROM orders
     WHERE EXTRACT(YEAR FROM order_date) = 2023
       AND EXTRACT(MONTH FROM order_date) = 5;
    
    -- AFTER:
    EXPLAIN
    SELECT *
      FROM orders
     WHERE order_date BETWEEN '2023-05-01'::DATE
                         AND '2023-05-31'::DATE;

    為什麼它更快:
    BETWEEN 用於索引日期欄位時,能夠直接進行索引範圍掃描,跳過對每一行的函數呼叫。

    5. 依賴 EXISTS 而不是 JOIN 來檢查存在性

    問題:
    如果你只需要確認另一個表中是否存在至少一筆相關的記錄,使用JOIN可能會獲取比所需更多的資料。

    解決方案:
    使用EXISTS在找到匹配時立即停止。

    範例

    -- BEFORE:
    EXPLAIN
    SELECT COUNT(DISTINCT o.order_id)
      FROM orders AS o
      JOIN order_items AS i
        ON o.order_id = i.order_id;
    
    -- AFTER:
    EXPLAIN
    SELECT COUNT(DISTINCT o.order_id)
      FROM orders AS o
     WHERE EXISTS (
           SELECT 1
             FROM order_items AS i
            WHERE i.order_id = o.order_id
           );

    為什麼它更快:
    查詢不會檢索不必要的行。一旦找到匹配的記錄,它就知道條件已滿足。

    6. 其他實用技巧

    1. 僅選擇您需要的欄位
    • SELECT * 會載入所有欄位,若你只需要少數幾個欄位,這可能會拖慢查詢速度。

    2. 新增 LIMIT

    • 如果你只需要一小部分行,請指定LIMIT。這讓資料庫可以跳過掃描所有內容。

    3. 避免在條件中使用函數

    • 對於字串操作,優先使用 LIKE 'ABC%' 而非 SUBSTRING(field, 1, 3) = 'ABC' ,以便保持索引的可用性。

    4. 優化聚合

    • 使用 FILTER(在 PostgreSQL 中)或 CASE 來進行條件計數,這可能比多個 UNION 或重複的子查詢更有效率。

    範例

    -- BEFORE:
    EXPLAIN
    SELECT SUM(CASE WHEN status = 'NEW' THEN 1 END) AS new_orders,
           SUM(CASE WHEN status = 'CLOSED' THEN 1 END) AS closed_orders
      FROM orders;
    
    -- AFTER:
    EXPLAIN
    SELECT COUNT(*) FILTER (WHERE status = 'NEW')    AS new_orders,
           COUNT(*) FILTER (WHERE status = 'CLOSED') AS closed_orders
      FROM orders;

    5. 使用邏輯表達式代替 CASE 進行簡單的布爾檢查

    • 例如, (table1.is_deleted OR table2.is_deleted) 比一個大的 CASE 表達式更清晰且更快。

    7. 替代 DISTINCT 的方法:ROW_NUMBER()

    目標:
    高效提取唯一值,特別是在大型數據集中。

    解決方案:
    有時候使用ROW_NUMBER()(並按鍵列分區)比使用DISTINCT更快,特別是當這些列被索引時。

    範例

    -- BEFORE:
    EXPLAIN
    SELECT COUNT(DISTINCT user_id)
      FROM logins
     WHERE login_date BETWEEN '2023-01-01'::DATE
                         AND '2023-01-31'::DATE;
    
    -- AFTER:
    EXPLAIN
    SELECT COUNT(user_id)
      FROM (
           SELECT user_id,
                  ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY user_id) AS rn
             FROM logins
            WHERE login_date BETWEEN '2023-01-01'::DATE
                                AND '2023-01-31'::DATE
           ) AS tmp
     WHERE rn = 1;

    為什麼它更快:
    ROW_NUMBER() 通過在每個群組內分配行號來避免繁重的去重排序操作。

    我的想法

    SQL 查詢優化是任何數據庫「支援」專案中的主要步驟。通過使用策略,例如用虛擬表替換INANY(ARRAY[]),利用BETWEEN處理日期,選擇EXISTS而非完整的JOIN,以及使用FILTER進行條件聚合,您很可能會看到性能的顯著提升。

    記住:

    • 避免使用 SELECT *
    • 明智地使用索引
    • WHERE子句中要注意函數調用
    • 盡快過濾數據
    • 保持查詢的可讀性和高效性