當輸入 SELECT * FROM users WHERE id = 42;並執行時,這條看似簡單的 SQL 語句,實際上會在 PostgreSQL 內部觸發一段複雜而精密的處理流程。該過程涉及多個後台進程、精細的內存管理機制,以及數十年數據庫優化研究的成果。
查詢執行的五個階段
無論查詢複雜與否,在 PostgreSQL 中都會經歷同一條基本路徑:
解析(Parsing) → 分析(Analysis) → 重寫(Rewriting) → 規劃(Planning) → 執行(Execution)
SQL 文本從一端進入,查詢結果從另一端返回。每一個階段內部都包含大量細緻而關鍵的處理邏輯。
查詢的起點:SQL 發送過程
以示例查詢語句為例,從提交時刻開始追蹤整個執行過程。應用程序首先與 PostgreSQL 服務器建立連接,隨後通過 PostgreSQL 通信協議發送查詢請求。
需要重點關注的是:當發送語句 SELECT * FROM users WHERE id = 42;時,PostgreSQL 會原封不動地接收該純文本格式的語句。無論通過 psql 終端輸入、應用程序調用,還是藉助 ORM 框架執行,SQL 語句最終都會以文本字符串的形式傳遞至服務器。
服務器在接收文本後,會進行基礎校驗,例如字符編碼是否合法、語句格式是否完整。隨後,正式進入查詢處理流程。
第一階段:解析 —— 從文本到結構
解析器是查詢處理的首個環節,核心任務是將 SQL 文本轉換為結構化的解析樹(Parse Tree)。
在該階段,解析器會逐字符讀取 SQL 語句,並依據 PostgreSQL 定義的 SQL 語法規則進行匹配和拆解,識別其中的關鍵字(如 SELECT、FROM、WHERE)、表名、列名、運算符等語法要素。
這一過程類似於語言學中的句法分析,只關注語法結構本身,而不涉及語義含義。
例如,SELECT name FROM users WHERE id = 42;解析完成後,系統可以明確:
- 存在一個 SELECT 子句,包含列引用
name。 - 存在一個 FROM 子句,引用表
users。 - 存在一個 WHERE 子句,包含條件表達式
id = 42。
但此時解析器並不知道 users 表是否真實存在、name 是否為有效列名,也不瞭解涉及字段的數據類型。這些語義層面的驗證工作,將由下一階段完成。
第二階段:分析 —— 語義校驗與綁定
分析器在解析樹的基礎上,構建語義有效的查詢樹(Query Tree),這是從“語法正確”邁向“語義正確”的關鍵階段。
該階段主要完成以下工作:
- 對象解析:在系統目錄中查找
users表,校驗其是否存在;確認name、id是否為合法列。 - 類型檢查:校驗
id = 42是否成立,例如id的數據類型是否支持與整數進行比較,對應的比較運算符是否存在。 - 權限校驗:確認當前會話是否具備訪問
users表及相關列的 SELECT 權限。 - 語義信息補充:為查詢樹補充對象標識信息,如表和列對應的 OID、字段類型等。
若任一環節失敗(表不存在、字段拼寫錯誤、權限不足等),查詢將在此階段終止並返回錯誤。
完成該階段後,查詢的含義已被 PostgreSQL 完整理解,接下來進入自動轉換處理。
第三階段:重寫 —— 自動規則轉換
重寫器在語義有效的查詢樹基礎上,應用一系列自動化轉換規則,生成最終待執行的查詢結構。常見的轉換包括:
- 視圖展開:當查詢對象為視圖時,重寫器會將視圖查詢轉換為對底層基表的查詢。例如,若視圖
active_users的定義為SELECT * FROM users WHERE status = 'active',則查詢SELECT * FROM active_users會被重寫為直接查詢users表並附加過濾條件status = 'active'。 - 行級安全策略(RLS):若表定義了安全策略,重寫器會自動注入額外的 WHERE 條件以實現訪問控制。例如,存在按租户隔離數據的策略時,原查詢
SELECT * FROM users WHERE id = 42可能被重寫為SELECT * FROM users WHERE id = 42 AND tenant_id = 123。 - 用户自定義規則:通過規則系統定義的查詢重寫邏輯(在現代應用中相對較少使用)。
對於簡單查詢,該階段可能不會發生明顯變化;但在包含視圖、安全策略的複雜系統中,重寫可能對查詢結構產生顯著影響。
第四階段:規劃 —— 尋找最優執行路徑
規劃器負責解決一個核心問題:如何以最低成本執行該查詢。
這一階段是 PostgreSQL 中最複雜、最具智能化特徵的部分,涉及多維度決策。
訪問路徑選擇
對於查詢中涉及的每張表,規劃器需要決定數據的讀取方式。例如,是對 users 表執行全表順序掃描,還是利用 id 字段上的索引直接定位目標數據行。規劃器需要決定採用何種方式訪問數據:
- 順序掃描(Sequential Scan)
- 索引掃描(Index Scan / Bitmap Scan)
規劃器會綜合評估表規模、索引可用性及選擇性。有時,即便存在索引,順序掃描也可能更高效。
連接策略選擇
在多表查詢中,規劃器需同時確定:
- 表的連接順序
- 每一步連接所採用的算法
連接順序的影響至關重要。例如,先連接表 A 和表 B,再與表 C 連接,和先連接表 B 和表 C,再與表 A 連接,兩種方式的執行效率可能存在巨大差異。規劃器會評估多種連接順序,篩選出最優方案。
針對每個連接操作,PostgreSQL 支持的主要連接算法包括:
- 嵌套循環連接(Nested Loop):適用於小數據集,或當連接操作的其中一方數據量極少的場景。
- 哈希連接(Hash Join):在內存充足的情況下,對中大型數據集的連接操作具有較高效率。
- 歸併連接(Merge Join):適用於兩個輸入數據集均已排序的場景。
不同連接順序和算法組合,對整體性能影響巨大,規劃器會評估多種可能性。
統計信息的作用
所有規劃決策高度依賴統計信息。PostgreSQL 通過 ANALYZE 收集並維護表統計數據,包括:
- 表的總行數
- 各字段的不同值數量
- 數據分佈情況
這些信息用於估算過濾條件和連接操作的結果規模,是成本評估的基礎。統計信息不準確將直接導致規劃決策偏差。
成本估算與最終計劃
規劃器會對多種候選執行計劃進行成本估算,綜合考慮:
- 磁盤 I/O 成本(從磁盤或緩存中讀取數據頁)
- CPU 計算成本(數據行處理、條件表達式計算)
- 內存消耗(排序、哈希操作)
最終選擇成本最低的方案作為執行計劃。當查詢涉及大量表連接時,PostgreSQL 會啓用遺傳查詢優化器(Genetic Query Optimizer)以避免組合爆炸。
規劃結果可通過 EXPLAIN 查看,例如:
EXPLAIN SELECT name FROM users WHERE id = 42;
第五階段:執行 —— 生成結果
執行器依據執行計劃逐步獲取數據,並向客户端返回結果。PostgreSQL 採用拉取式(Pull-based)執行模型。
在該模型中,上層節點按需向下層節點請求數據,而非由下層主動推送。這種機制具備良好的內存效率,並天然支持 LIMIT 等提前終止操作。
仍以查詢 SELECT name FROM users WHERE id = 42 為例,其執行計劃的輸出結果可能如下:
QUERY PLAN
-----------------------------------------------------------
Index Scan using users_id_idx on users (cost=0.00..8.27 rows=1 width=64)
Filter: (id = 42)
(2 rows)
以示例查詢為例,執行流程為:
- 執行計劃的頂層節點(負責向客户端返回結果)發起數據行請求。
- 該請求觸發索引掃描節點,向其下層節點發起數據請求。
- 索引掃描節點利用 users_id_idx 索引定位滿足條件 id = 42 的數據行。
- 從磁盤或緩存中讀取目標數據行,並應用過濾條件進行校驗。
- 數據結果沿執行計劃逆向傳遞:索引掃描節點 → 客户端。
拉取式模型具有顯著的內存效率優勢,因為 PostgreSQL 僅在下游節點需要數據時才執行處理操作。同時,該模型也簡化了結果集限制與提前終止等功能的實現 —— 只需停止向上遊節點請求數據即可。
執行器逐行處理數據,按照通信協議的要求格式化結果,然後通過網絡連接將數據發送至客户端。
所有結果發送完成後,PostgreSQL 會自動執行清理操作:
- 銷燬臨時內存上下文
- 釋放執行過程中獲取的各類鎖資源
- 刪除執行期間生成的臨時文件
至此,當前後端處理進程恢復空閒狀態,準備接收下一個查詢請求。
全流程回顧
以 SELECT name FROM users WHERE id = 42 為例,完整流程如下:
- 查詢發送:應用程序建立連接,以純文本形式發送查詢語句。
- 解析:將文本轉換為解析樹,完成語法結構校驗。
- 分析:驗證語義合法性,補充元數據信息,生成查詢樹。
- 重寫:執行自動轉換操作,如視圖展開、安全策略應用。
- 規劃:評估訪問路徑、連接策略,結合統計信息生成最優執行計劃。
- 執行:基於拉取式模型執行計劃,生成並返回查詢結果。
- 清理:釋放資源,恢復進程空閒狀態。
所有查詢均遵循該路徑,區別僅在於各階段的複雜程度。
核心價值
理解 PostgreSQL 查詢執行流程,能夠帶來以下核心價值:
- 編寫更高效的查詢語句:掌握規劃器的工作原理,可針對性地優化查詢結構,例如理解部分查詢無法使用索引的原因、連接順序對性能的影響,以及公用表表達式(CTE)與子查詢的適用場景差異。
- 精準診斷性能問題:當查詢執行緩慢時,可通過 EXPLAIN 命令定位問題環節,判斷是規劃器選擇了低效執行路徑、統計信息過期,還是缺少必要的索引。
- 設計更合理的數據庫架構:基於查詢執行機制的理解,能夠優化索引設計、表分區方案以及視圖的使用方式。
- 認知數據庫底層複雜度:一條簡單的 SELECT 語句背後,隱藏着連接管理、內存分配、語法校驗、語義分析、規則應用、成本優化以及拉取式執行等一系列複雜機制,而這一切都在後台透明運行。
原文鏈接:
https://internals-for-interns.com/posts/sql-query-roadtrip-in...
作者:Jesús Espino