Query optimizer 是引擎裡負責挑選執行方式的元件。它讀進查詢,產出一個 query plan,而它可以任意重排步驟,只受一個約束:輸出的 relation 要與語意模型算出來的一致。

它比較的依據是另外存著的一份統計資訊——那份摘要既不在查詢的文字裡也不在資料表裡,而統計多新,它的估計就多準。

概念位置

最佳化器的存在是 SQL 宣告式性質的直接結果。查詢只描述要什麼結果,所以「怎麼算」這個決定必須有人做,而做這個決定的就是它。

它的選擇依據是估計的代價,而估計來自統計資訊——每張表大概多少列、某個欄位的值大概怎麼分布。所以它的判斷品質取決於那些統計有多新,與查詢寫得多好無關。它的產物是一份 query plan,而樹上每個節點處理的都是一個 relation。

可觀察訊號與例子

書店的顧客表五十列、訂單表二十萬列。同一個連接寫成 FROM 訂單 JOIN 顧客 與 FROM 顧客 JOIN 訂單,在跑過 ANALYZE 的資料庫上 SQLite 兩次回報一模一樣的計畫——都掃二十萬列那張、對五十列那張做查找。書寫位置決定語意,執行方式由最佳化器另外決定。

統計缺席時它只好照文字的順序掃——那一組對照的完整實驗在 SQL.1 宣告式的紅利與代價:書寫、求值與執行順序各自由誰決定。

換掉可用的資源,計畫也會變。建好 訂單(顧客編號) 索引之後,同一段查詢從三行縮成兩行——SCAN 顧客 接 SEARCH 訂單 USING COVERING INDEX ix,臨時索引不必再建。

它也可以做等價變換,例如把某些 outer join 轉成內連接,前提是查詢的其他部分已經讓外連接補的那些列不可能出現在結果裡。

設計責任

查詢沒動而某天變慢時,變的是統計而不是查詢——最佳化器照估計值選,估計舊了選擇就跟著舊。所以這種情形的歸因起點是統計資訊的新舊,不是查詢的寫法。

反過來,最佳化器選錯而統計是新的時候,才輪到用提示或改寫去引導它,而那些手段會把原本自動適應資料變化的查詢釘死在一個選擇上。