支援的版本:目前 (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 語法,在一定程度上控制查詢規劃器。要了解這點的重要性,我們首先需要一些背景知識。

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

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

規劃器可以自由地以任何順序聯結給定的資料表。例如,它可以產生一個查詢計畫,將 A 聯結到 B,使用 WHERE 條件 a.id = b.id,然後將 C 聯結到這個聯結後的資料表,使用另一個 WHERE 條件。或者它可以將 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 或 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_limitFROM 項目,規劃器會嘗試避免陷入巨大的聯結搜尋問題,而不折疊子查詢。您可以透過上下調整此執行階段參數,來權衡規劃時間與計畫的品質。

from_collapse_limitjoin_collapse_limit 的命名相似,因為它們幾乎做同樣的事情:一個控制規劃器何時會 展開 子查詢,另一個控制它何時會展開明確的聯結。通常,您可以將 join_collapse_limit 設定為等於 from_collapse_limit(以便明確的聯結和子查詢的行為類似),或者將 join_collapse_limit 設定為 1(如果您想用明確的聯結來控制聯結順序)。但是,如果您正在嘗試微調規劃時間和執行時間之間的權衡,您可以將它們設定為不同的值。

提交更正

如果您在文件中發現任何不正確、與您使用特定功能的經驗不符或需要進一步澄清的地方,請使用此表單報告文件問題。