SQL test 02 - Filter Simplification (邏輯條件簡化)
#
在撰寫 SQL 時,我們有時會因為業務邏輯的疊加,寫出看起來稍微有些冗長或重複的 WHERE 條件。
但你知道 Oracle 資料庫的優化器 (Optimizer) 其實非常聰明,會自動幫你進行布林邏輯的簡化 (Boolean Simplification / Logical Absorption) 嗎?
讓我們透過以下兩個查詢來進行測試比較。
測試情境
#
我們想要查詢 USERS_DETAILS_2023 這張表中,特定條件下的總人數。
測試 SQL 1 (條件冗長版)
#
這個查詢的條件為:(性別是男性 AND 居住在 Calgary) OR (性別是男性)。
從人類的邏輯來看,只要「性別是男性」這個條件成立,整個 WHERE 條件就成立了,LOCATION 的判斷其實是多餘的。
1
2
3
|
SELECT COUNT(*)
FROM pollo.USERS_DETAILS_2023
WHERE (GENDER = 'Male' AND LOCATION = 'Calgary, AB') OR GENDER = 'Male';
|
執行結果:
1
2
3
4
5
|
┌───────────┐
│ COUNT (*) │
├───────────┤
│ 126984 │
└───────────┘
|
測試 SQL 2 (條件精簡版)
#
我們直接把邏輯簡化,只保留核心條件。
1
2
3
|
SELECT COUNT(*)
FROM pollo.USERS_DETAILS_2023
WHERE GENDER = 'Male';
|
執行結果:
1
2
3
4
5
|
┌───────────┐
│ COUNT (*) │
├───────────┤
│ 126984 │
└───────────┘
|
兩者的查詢結果完全一致,這很合理。但更驚人的是它們的執行計畫。
執行計畫 (Explain Plan) 比較
#
SQL 1 的執行計畫
#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
┌───────────────────────────────────────────────────────────────────────────────────────────┐
│ PLAN TABLE OUTPUT │
├───────────────────────────────────────────────────────────────────────────────────────────┤
│ Plan hash value: 4071893995 │
│ │
│ ----------------------------------------------------------------------------------------- │
│ | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | │
│ ----------------------------------------------------------------------------------------- │
│ | 0 | SELECT STATEMENT | | 1 | 52 | 3816 (1)| 00:00:01 | │
│ | 1 | SORT AGGREGATE | | 1 | 52 | | | │
│ |* 2 | TABLE ACCESS FULL| USERS_DETAILS_2023 | 161K| 8210K| 3816 (1)| 00:00:01 | │
│ ----------------------------------------------------------------------------------------- │
│ │
│ Predicate Information (identified by operation id): │
│ --------------------------------------------------- │
│ │
│ 2 - filter("GENDER"=U'Male') │
│ │
│ Note │
│ ----- │
│ - dynamic statistics used: dynamic sampling (level=2) │
└───────────────────────────────────────────────────────────────────────────────────────────┘
|
SQL 2 的執行計畫
#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
┌───────────────────────────────────────────────────────────────────────────────────────────┐
│ PLAN TABLE OUTPUT │
├───────────────────────────────────────────────────────────────────────────────────────────┤
│ Plan hash value: 4071893995 │
│ │
│ ----------------------------------------------------------------------------------------- │
│ | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | │
│ ----------------------------------------------------------------------------------------- │
│ | 0 | SELECT STATEMENT | | 1 | 52 | 3816 (1)| 00:00:01 | │
│ | 1 | SORT AGGREGATE | | 1 | 52 | | | │
│ |* 2 | TABLE ACCESS FULL| USERS_DETAILS_2023 | 161K| 8210K| 3816 (1)| 00:00:01 | │
│ ----------------------------------------------------------------------------------------- │
│ │
│ Predicate Information (identified by operation id): │
│ --------------------------------------------------- │
│ │
│ 2 - filter("GENDER"=U'Male') │
│ │
│ Note │
│ ----- │
│ - dynamic statistics used: dynamic sampling (level=2) │
└───────────────────────────────────────────────────────────────────────────────────────────┘
|
總結與 Tuning 建議
#
你是不是驚訝地發現:SQL 1 和 SQL 2 的執行計畫(包含 Plan hash value 與 Cost)竟然完全一模一樣?
仔細看 Predicate Information 的部分:
即使我們在 SQL 1 寫了 (GENDER = 'Male' AND LOCATION = 'Calgary, AB') OR GENDER = 'Male',Oracle 最終送進去執行的過濾條件卻只有 filter("GENDER"=U'Male'),完全捨棄了對 LOCATION 的判斷!
為什麼會這樣?(背後原理)
#
這歸功於資料庫優化器 (Optimizer) 的查詢轉換 (Query Transformation) 功能。
在數學的布林代數 (Boolean Algebra) 中,有一個稱為吸收律 (Absorption Law) 的規則:
(A AND B) OR A = A
Oracle 的 CBO (Cost-Based Optimizer) 在解析 SQL 時,會進行邏輯過濾條件簡化 (Filter Simplification)。它發現 SQL 1 的條件符合吸收律,於是自動將其轉換為最簡形式 GENDER = 'Male',然後才去產生執行計畫。這也是為什麼兩個 SQL 最終的執行路徑與代價會完全相同。
(註:雖然標題有時會被泛稱為 Common Sub-Expression Elimination,但在這個具體情境下,更準確的優化術語是 Filter Simplification 邏輯條件簡化。)
最佳實踐 (Best Practices)
#
既然 Oracle 這麼聰明會自動幫我們優化,那我們是不是就可以隨便寫 SQL 了呢?
答案是:盡量不要!
- 降低解析成本 (Parse Time):優化器雖然聰明,但「分析與簡化邏輯」這個動作本身就需要消耗 CPU 資源與解析時間。如果條件非常複雜,可能會增加 Hard Parse 的成本與優化器的負擔。
- 提高可讀性與維護性:SQL 不只是寫給機器跑的,更是寫給人看的。乾淨、直覺 的 SQL 邏輯,可以讓接手維護的同事 (或是幾個月後的自己) 更快看懂這段查詢的真正意圖,避免在疊床架屋的
WHERE 條件中迷失。
結論:善用資料庫強大的優化能力,但保持良好的 SQL 撰寫習慣。在送出查詢前,先用人類的邏輯把 WHERE 條件精簡化,才是 SQL Tuning 與開發的上策!