首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 數據庫知識 >  >> Oracle 資料庫 >> Oracle 資料庫基礎

Oracle SQL 優化規則精華:WHERE 條件順序、SELECT 星號禁忌、減少資料庫往返三大鐵律

     編輯:Oracle 資料庫基礎

SQL 調優不僅是 DBA 的事,**開發人員寫 SQL 的習慣決定了 80% 的效能上限**。本文萃取三條**零成本、立竿見影、普遍適用**的寫碼鐵律,助您在編碼階段就寫出高效能 SQL。

鐵律一:WHERE 條件順序 —— 過濾力強的後寫 (RBO 遺產 / CBO 參考)

Oracle 解析 WHERE 子句**自下而上 (從最後一行向上)**。雖 CBO 會自動重排,但**明確排序能輔助優化器、穩定計畫、利於 Hint 干預**。

原則

  1. 表連接條件優先 (最下方): `WHERE e.deptno = d.deptno`
  2. 過濾力最強 (篩選率最低) 的條件次之: 如 `WHERE empno = 7788` (唯一鍵)、`WHERE status = 'CLOSED' AND closed_date > SYSDATE-1`
  3. 過濾力較弱、運算複雜的條件最後: 如 `WHERE UPPER(ename) LIKE '%SMITH%'`、子查詢 `EXISTS`、函數運算

實戰對比


-- 低效:弱過濾在前,強過濾在後 (執行 156.3 秒)
SELECT * FROM emp e
WHERE sal > 50000
AND job = 'MANAGER'
AND 25 < (SELECT COUNT(*) FROM emp WHERE mgr = e.empno);

-- 高效:強過濾 (子查詢) 前置,弱過濾後置 (執行 10.6 秒)
SELECT * FROM emp e
WHERE 25 < (SELECT COUNT(*) FROM emp WHERE mgr = e.empno)
AND sal > 50000
AND job = 'MANAGER';

現代補充: CBO 11g+ 已能自動重排,**但複雜視圖/子查詢/外部連接時,手工排序仍具穩定計畫價值**。

鐵律二:SELECT 子句禁用 `*` —— 明確列出欄位


-- 惡例
SELECT * FROM orders WHERE cust_id = 123;

-- 佳例
SELECT order_id, order_date, total_amt, status FROM orders WHERE cust_id = 123;

為何 `*` 是效能殺手?

  1. 解析開銷: 優化器需查詢 Data Dictionary (`ALL_TAB_COLUMNS`) 將 `*` 展開為所有欄位,**硬解析時額外消耗 Dictionary Cache Latch**。
  2. 網路傳輸: 傳輸不需要的欄位 (LOB、長字串、大量欄位) → 頻寬浪費、客戶端記憶體壓力。
  3. 索引失效風險: 覆蓋索引 要求 `SELECT` 欄位全部包含於索引中。`*` 導致無法走 Index Only Scan (Index Fast Full Scan)。
  4. 結構變更脆弱: 表新增/刪除欄位 → 應用層 `ResultSet.getString(index)` 索引錯位、ORA-00904/ORA-01722 運行時爆炸。**明確欄位 = 編譯期/啟動期即發現不匹配**。
  5. 安全洩漏: 敏感欄位 (薪資、密碼雜湊、PII) 可能被非預期查詢洩漏。

鐵律三:減少資料庫往返 —— 批次化、集合操作、PL/SQL 封裝

每次 SQL 執行 = **網路 RTT + 解析/綁定/執行/獲取開銷**。減少 Round-trip 是提升吞吐的核心槓桿。

策略對比

模式Round-trip適用場景範例
單筆循環N 次極少數據、邏輯極複雜`for id in ids: UPDATE ... WHERE id = ?`
批次繫結1 次大量 DML (INSERT/UPDATE/DELETE)`PreparedStatement.addBatch()` / `FORALL`
集合操作 (SQL)1 次集合邏輯可用 SQL 表達`INSERT SELECT` / `MERGE` / `DELETE WHERE IN`
PL/SQL 封裝1 次複雜業務邏輯、多步驟、需事務原子性`CALL pkg_batch.process(:param)`
Pipeline Function1 次大量資料轉換/產生,流式返回客戶端`SELECT * FROM TABLE(pkg_gen.data(:p))`

Java 批次繫結範例 (addBatch)


String sql = "INSERT INTO log (id, msg, created) VALUES (seq_log.nextval, ?, SYSTIMESTAMP)";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
  for (LogEntry e : entries) {
    ps.setString(1, e.getMsg());
    ps.addBatch();
  }
  int[] counts = ps.executeBatch(); // 單次網路往返執行所有
  conn.commit();
}

PL/SQL FORALL 範例


DECLARE
  TYPE t_ids IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
  v_ids t_ids;
BEGIN
  -- 假設 v_ids 已填充 10000 筆
  FORALL i IN v_ids.FIRST .. v_ids.LAST
    UPDATE accounts SET balance = balance - 100 WHERE id = v_ids(i);
  -- 單次上下文切換,批次執行
END;

綜合應用:重構前後對比


-- 重構前:N+1 問題、SELECT *、弱過濾前置
FOR rec IN (SELECT * FROM orders WHERE status = 'NEW') LOOP
  SELECT * FROM customers WHERE cust_id = rec.cust_id; -- 循環查詢
  UPDATE orders SET status = 'PROCESSING' WHERE order_id = rec.order_id; -- 循環更新
END LOOP;

-- 重構後:單次 SQL、明確欄位、集合操作
MERGE INTO orders o
USING (SELECT order_id FROM orders WHERE status = 'NEW') src
ON (o.order_id = src.order_id)
WHEN MATCHED THEN UPDATE SET status = 'PROCESSING', 
                            processed_dt = SYSTIMESTAMP
WHERE o.status = 'NEW'; -- 單次執行,原子性保證

代碼審查清單 (強制項)

  1. [ ] **無 `SELECT *`** → 明確欄位清單。
  2. [ ] **WHERE 條件順序合理** → 連接條件最下、強過濾前置。
  3. [ ] **無 N+1 循環查詢** → 改用 JOIN、MERGE、批次繫結、PL/SQL 封裝。
  4. [ ] **無隱式轉換** → 字面值加類型標記 (`DATE '...'`, `TIMESTAMP '...'`)。
  5. [ ] **批次 DML 使用 FORALL / addBatch** → 單次 Round-trip。
  6. [ ] **大結果集使用游標/分頁/流式** → 避免一次性 Fetch 所有行。

這三條鐵律不需要權限、不需要工具、不需要重啟,**只需要開發習慣**。從今天的第一行 SQL 開始貫徹,您的系統效能基線將顯著上移。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved