快轉到主要內容

SQL test 02 - Filter Simplification (邏輯條件簡化)

·1982 字·4 分鐘·
PolloChang
作者
PolloChang
我是一隻雞

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 了呢? 答案是:盡量不要!

  1. 降低解析成本 (Parse Time):優化器雖然聰明,但「分析與簡化邏輯」這個動作本身就需要消耗 CPU 資源與解析時間。如果條件非常複雜,可能會增加 Hard Parse 的成本與優化器的負擔。
  2. 提高可讀性與維護性:SQL 不只是寫給機器跑的,更是寫給人看的乾淨直覺 的 SQL 邏輯,可以讓接手維護的同事 (或是幾個月後的自己) 更快看懂這段查詢的真正意圖,避免在疊床架屋的 WHERE 條件中迷失。

結論:善用資料庫強大的優化能力,但保持良好的 SQL 撰寫習慣。在送出查詢前,先用人類的邏輯把 WHERE 條件精簡化,才是 SQL Tuning 與開發的上策!