Optimizer 無法在執行前實測每個 possible plan,只能估算 intermediate result size、指派 cost,再從模型中選最便宜的計畫。
Logical rewrite 提早減少工作
Relational-algebra equivalence 讓 optimizer 可以:
- 把 selection 推近 scan;
- 提早移除不需要的 column;
- 調整 inner join 順序;
- 把 Cartesian product 加 filter 改寫成 join。
把 topic = 'databases' 推到 join 下方,可能在昂貴 operator 前就縮小 input。但 outer join、limit、volatile function 與 NULL semantics 都會限制合法 rewrite。
Cardinality 決定後面所有選擇
在 uniformity assumption 下,equality selection 可估成:
T(σ A=c (R)) ≈ T(R) / V(R,A)
Equality join 可估成:
T(R ⋈ S) ≈ T(R) × T(S) / max(V(R,A), V(S,A))
T 是 tuple count,V 是 distinct value 數量。公式是推理模型,不是承諾;skew、correlated column、missing value 與 stale statistics 都可能破壞 assumption。
白話來說: Optimizer 拿著預估車流量的地圖選路。某個路口標示十台車,實際卻有一萬台,後面每個路線決定都可能跟著錯。
Join order 改變 intermediate size
三張 table 最後結果可以完全相同,但 intermediate result 差好幾個數量級。先 join 兩張大表可能產生巨大 temporary result;先和 selective relation join,則能縮小後續工作。
Dynamic programming 可以比較較小 join set 的 subplan,但 search space 成長很快。大型 query 的 optimizer 必須 prune alternative 並使用 heuristic。
Cost 不等於 elapsed time
Cost model 會組合 sequential/random page access、CPU、memory、parallelism 與預期 output。Cost unit 用來比較 plan,不是 millisecond。
EXPLAIN (ANALYZE, BUFFERS, VERBOSE, TIMING OFF)
SELECT t.name, count(*)
FROM topic t
JOIN paper_topic pt ON pt.topic_id = t.topic_id
JOIN paper p ON p.paper_id = pt.paper_id
WHERE p.year >= 2024
GROUP BY t.name;
從下往上讀,在第一個 estimate rows 與 actual rows 分岔的位置停下來。Top-level estimate 很差,原因通常是更早的 scan 或 join,不是 top node 本身。
修 evidence,不只修 symptom
Estimate 錯誤時依序檢查:
- 載入代表性資料後有沒有收集 statistics?
- Test distribution 是否接近 production?
- Predicate 彼此 correlated,模型卻假設 independent 嗎?
- Cast 或 function 是否包住 indexed column?
- Query 是否真的要回傳大量資料,所以 scan 才是正解?
Hint 能強迫 plan,也會凍結一個可能隨資料改變而失效的決策。先改善 statistics、query shape 與 index。
複習卡
- Rewrite 必須保留 semantics。
- Intermediate cardinality 決定 downstream cost。
- Uniformity 與 independence 是 assumption,不是事實。
- Join order 重要,因為 intermediate result 大小不同。
- Planner cost 是比較單位,不是 elapsed time。
- 找第一個 estimate/actual 分岔點。
- 優先修 evidence,不要先強迫 plan。