2025年9月25日: PostgreSQL 18 釋出!
支援的版本: 當前 (18) / 17 / 16 / 15 / 14 / 13
開發版本: devel
不支援的版本: 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1 / 8.0 / 7.4 / 7.3 / 7.2 / 7.1

14.3. 使用顯式 JOIN 子句控制查詢規劃器 #

透過使用顯式的 JOIN 語法,可以在一定程度上控制查詢規劃器。要了解其重要性,我們首先需要一些背景知識。

在一個簡單的連線查詢中,例如

SELECT * FROM a, b, c WHERE a.id = b.id AND b.ref = c.id;

規劃器可以自由地以任何順序連線給定的表。例如,它可以生成一個連線 A 和 B 的查詢計劃,使用 WHERE 條件 a.id = b.id,然後使用另一個 WHERE 條件將 C 連線到此連線表。或者它可以先連線 B 和 C,然後將 A 連線到結果。或者它可以連線 A 和 C,然後再與 B 連線——但這將效率低下,因為必須形成 A 和 C 的完整笛卡爾積,因為 WHERE 子句中沒有適用的條件來允許連線的最佳化。(PostgreSQL 執行器中的所有連線都發生在兩個輸入表之間,因此有必要以其中一種方式構建結果。)關鍵在於,這些不同的連線可能性會產生語義上等同的結果,但執行成本可能差異巨大。因此,規劃器將探索所有這些可能性,以找到最高效的查詢計劃。

當查詢僅涉及兩到三張表時,沒有太多需要擔心的連線順序。但隨著表數量的增加,可能連線順序的數量呈指數級增長。當輸入表超過十個左右時,對所有可能性進行詳盡搜尋就不再可行,即使是六到七張表,規劃也可能耗費不必要的長時間。當輸入表過多時,PostgreSQL 規劃器將從詳盡搜尋切換到有限可能性上的 遺傳 機率搜尋。(切換閾值由 geqo_threshold 執行時引數設定。)遺傳搜尋耗時較短,但未必能找到最佳計劃。

當查詢涉及外連線時,規劃器的自由度比普通(內連線)連線要小。例如,考慮

SELECT * FROM a LEFT JOIN (b JOIN c ON (b.ref = c.id)) ON (a.id = b.id);

儘管此查詢的限制表面上與前一個示例相似,但其語義不同,因為 A 中沒有與 B 和 C 連線匹配的行的每一行都必須發出。因此,規劃器在此沒有連線順序的選擇:它必須先連線 B 和 C,然後將 A 連線到該結果。因此,此查詢的規劃時間比前一個查詢要短。在其他情況下,規劃器可能能夠確定不止一種連線順序是安全的。例如,給定

SELECT * FROM a LEFT JOIN b ON (a.bid = b.id) LEFT JOIN c ON (a.cid = c.id);

先連線 A 到 B 或先連線 A 到 C 都是有效的。目前,只有 FULL JOIN 完全約束了連線順序。大多數涉及 LEFT JOINRIGHT JOIN 的實際情況在一定程度上可以重新排列。

顯式的內連線語法(INNER JOINCROSS JOIN 或未加裝飾的 JOIN)在語義上與在 FROM 中列出輸入關係相同,因此它不會約束連線順序。

即使大多數 JOIN 型別不能完全約束連線順序,也可以指示 PostgreSQL 查詢規劃器將所有 JOIN 子句都視為約束連線順序。例如,這三個查詢在邏輯上是等價的

SELECT * FROM a, b, c WHERE a.id = b.id AND b.ref = c.id;
SELECT * FROM a CROSS JOIN b CROSS JOIN c WHERE a.id = b.id AND b.ref = c.id;
SELECT * FROM a JOIN (b JOIN c ON (b.ref = c.id)) ON (a.id = b.id);

但如果我們告訴規劃器遵循 JOIN 順序,第二個和第三個查詢的規劃時間會比第一個短。對於只有三張表的情況,這種效果不值得擔心,但對於多張表的情況,它可能非常有幫助。

要強制規劃器遵循顯式 JOIN 所定義的連線順序,請將 join_collapse_limit 執行時引數設定為 1。(其他可能的值將在下文討論。)

為了減少搜尋時間,您不需要完全約束連線順序,因為在普通 FROM 列表的項中使用 JOIN 運算子是可以的。例如,考慮

SELECT * FROM a CROSS JOIN b, c, d, e WHERE ...;

join_collapse_limit = 1 時,這會強制規劃器在將 A 連線到其他表之前先連線 A 和 B,但不會約束其他方面的選擇。在此示例中,可能的連線順序數量減少了 5 倍。

以這種方式約束規劃器的搜尋是減少規劃時間和引導規劃器獲得良好查詢計劃的有用技術。如果規劃器預設選擇了糟糕的連線順序,您可以強制它透過 JOIN 語法選擇更好的順序——當然,前提是您知道更好的順序。建議進行實驗。

一個與此密切相關並影響規劃時間的問題是子查詢摺疊到其父查詢中。例如,考慮

SELECT *
FROM x, y,
    (SELECT * FROM a, b, c WHERE something) AS ss
WHERE somethingelse;

這種情況可能由包含連線的檢視引起;檢視的 SELECT 規則將被插入到檢視引用的位置,從而產生一個類似上面的查詢。通常,規劃器會嘗試將子查詢摺疊到父查詢中,得到

SELECT * FROM x, y, a, b, c WHERE something AND somethingelse;

這通常比單獨規劃子查詢能獲得更好的計劃。(例如,外部 WHERE 條件可能意味著先連線 X 和 A 可以消除 A 中的許多行,從而避免了形成子查詢的完整邏輯輸出。)但同時,我們增加了規劃時間;這裡,我們有一個五路連線問題取代了兩個獨立的 ثلاثة路連線問題。由於可能性數量呈指數級增長,這產生了巨大的影響。規劃器試圖透過不折疊子查詢(如果結果是父查詢中的 from_collapse_limit 個以上的 FROM 項)來避免陷入巨大的連線搜尋問題。您可以透過調整此執行時引數來權衡規劃時間與計劃質量。

from_collapse_limitjoin_collapse_limit 的命名相似,因為它們的作用幾乎相同:一個控制規劃器何時 展平 子查詢,另一個控制它何時展平顯式連線。通常,您會將 join_collapse_limit 設定為等於 from_collapse_limit(使顯式連線和子查詢行為相似),或者將 join_collapse_limit 設定為 1(如果您想用顯式連線控制連線順序)。但如果您試圖微調規劃時間和執行時間之間的權衡,您可能需要設定不同的值。

提交更正

如果您在文件中發現任何不正確之處、與您對特定功能的體驗不符之處或需要進一步說明之處,請使用 此表格 報告文件問題。