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

58.4. 外部資料包裝器查詢規劃 #

FDW 回撥函式 GetForeignRelSizeGetForeignPathsGetForeignPlanPlanForeignModifyGetForeignJoinPathsGetForeignUpperPathsPlanDirectModify 必須能夠融入 PostgreSQL 規劃器的運作中。以下是一些關於它們必須做什麼的說明。

可以使用 rootbaserel 中的資訊來減少必須從外部表中獲取的資訊量(從而降低成本)。baserel->baserestrictinfo 尤其有趣,因為它包含應該用於過濾要獲取的行的限制條件(WHERE 子句)。(FDW 本身不需要強制執行這些條件,因為核心執行器可以改為檢查它們。)baserel->reltarget->exprs 可用於確定需要獲取哪些列;但請注意,它僅列出必須由 ForeignScan 計劃節點發出的列,而不包括在條件評估中使用但未由查詢輸出的列。

各種私有欄位可供 FDW 規劃函式用於保留資訊。通常,您在 FDW 私有欄位中儲存的任何內容都應該被 palloc'd,以便在規劃結束時被回收。

baserel->fdw_private 是一個 void 指標,可供 FDW 規劃函式儲存與特定外部表相關的資訊。核心規劃器除了在建立 RelOptInfo 節點時將其初始化為 NULL 外,不會觸及它。它對於將資訊從 GetForeignRelSize 傳遞到 GetForeignPaths 和/或從 GetForeignPaths 傳遞到 GetForeignPlan 非常有用,從而避免重新計算。

GetForeignPaths 可以透過在 ForeignPath 節點的 fdw_private 欄位中儲存私有資訊來識別不同訪問路徑的含義。fdw_private 被宣告為一個 List 指標,但實際上可以包含任何內容,因為核心規劃器不會觸及它。但是,最佳實踐是使用一個可由 nodeToString 轉儲的表示形式,以便與後端中可用的除錯支援一起使用。

GetForeignPlan 可以檢查選定的 ForeignPath 節點的 fdw_private 欄位,並可以生成 fdw_exprsfdw_private 列表,將它們放入 ForeignScan 計劃節點,在那裡它們將在執行時可用。這兩個列表都必須以 copyObject 知道如何複製的形式表示。fdw_private 列表沒有其他限制,並且不會被核心後端以任何方式解釋。fdw_exprs 列表(如果不是 NIL)應包含打算在執行時執行的表示式樹。這些樹將經過規劃器的後處理,使其完全可執行。

GetForeignPlan 中,通常可以將傳入的目標列表原樣複製到計劃節點中。傳入的 scan_clauses 列表包含與 baserel->baserestrictinfo 相同的子句,但可能會重新排序以提高執行效率。在簡單的情況下,FDW 可以直接從 scan_clauses 列表(使用 extract_actual_clauses)中剝離 RestrictInfo 節點,並將所有子句放入計劃節點的 qual 列表中,這意味著所有子句將在執行時由執行器檢查。更復雜的 FDW 可能能夠內部檢查其中一些子句,在這種情況下,可以從計劃節點的 qual 列表中刪除這些子句,以避免執行器浪費時間重新檢查它們。

例如,FDW 可能會識別一些形式為 foreign_variable = sub_expression 的限制子句,它確定在給定本地評估的 sub_expression 值的情況下,可以在遠端伺服器上執行這些子句。對此類子句的實際識別應該在 GetForeignPaths 期間發生,因為它會影響路徑的成本估計。路徑的 fdw_private 欄位可能包括指向已識別子句的 RestrictInfo 節點的指標。然後 GetForeignPlan 將該子句從 scan_clauses 中刪除,但將 sub_expression 新增到 fdw_exprs 中,以確保它被處理成可執行的形式。它可能還會將控制資訊放入計劃節點的 fdw_private 欄位,以告知執行函式在執行時做什麼。傳送到遠端伺服器的查詢將涉及類似 WHERE foreign_variable = $1 的內容,其中引數值將在執行時從 fdw_exprs 表示式樹的評估中獲得。

從計劃節點 qual 列表中刪除的任何子句必須改而新增到 fdw_recheck_quals 中,或由 RecheckForeignScan 重新檢查,以確保在 READ COMMITTED 隔離級別下的正確行為。當查詢涉及的另一個表發生併發更新時,執行器可能需要驗證原始子句是否仍然滿足該元組,可能針對一組不同的引數值。使用 fdw_recheck_quals 通常比在 RecheckForeignScan 中實現檢查更容易,但當外部連線被下推時,此方法將不足夠,因為在這種情況下,連線元組的某些欄位可能變為 NULL 而不完全拒絕該元組。

FDW 可以填充的另一個 ForeignScan 欄位是 fdw_scan_tlist,它描述了 FDW 為該計劃節點返回的元組。對於簡單的外部表掃描,這可以設定為 NIL,這意味著返回的元組具有為外部表宣告的行型別。非 NIL 值必須是一個目標列表(TargetEntrys 的列表),包含代表返回列的 Vars 和/或表示式。這可以用於,例如,表明 FDW 已省略了它注意到查詢不需要的某些列。另外,如果 FDW 可以比本地更便宜地計算查詢使用的表示式,它可以將這些表示式新增到 fdw_scan_tlist。請注意,連線計劃(由 GetForeignJoinPaths 建立的路徑生成)必須始終提供 fdw_scan_tlist 來描述它們將返回的列集。

FDW 應始終構造至少一個僅依賴於表限制子句的路徑。在連線查詢中,它還可以選擇構造依賴於連線子句的路徑,例如 foreign_variable = local_variable。此類子句不會出現在 baserel->baserestrictinfo 中,但必須在關係的連線列表中查詢。使用此類子句的路徑稱為引數化路徑。它必須使用 param_info 的適當值來識別選定連線子句中使用的其他關係;使用 get_baserel_parampathinfo 來計算該值。在 GetForeignPlan 中,連線子句的 local_variable 部分將被新增到 fdw_exprs 中,然後在執行時,該情況與普通限制子句的情況相同。

如果 FDW 支援遠端連線,GetForeignJoinPaths 應該以與 GetForeignPaths 對基本表的操作方式類似的方式為潛在的遠端連線生成 ForeignPath。有關預期連線的資訊可以像上面描述的那樣傳遞給 GetForeignPlan。但是,baserestrictinfo 對於連線關係無關緊要;相反,特定連線的相關連線子句作為單獨的引數(extra->restrictlist)傳遞給 GetForeignJoinPaths

FDW 可能還支援對掃描和連線級別之上的某些計劃操作的直接執行,例如分組或聚合。為了提供此類選項,FDW 應生成路徑並將其插入到適當的上層關係中。例如,代表遠端聚合的路徑應使用 add_path 插入到 UPPERREL_GROUP_AGG 關係中。這條路徑將在成本基礎上與透過讀取外部關係(請注意,必須也提供這樣的路徑,否則計劃時會出錯)的簡單掃描路徑進行的本地聚合進行比較。如果遠端聚合路徑獲勝(通常情況如此),它將透過呼叫 GetForeignPlan 以通常的方式轉換為計劃。建議的生成此類路徑的位置是在 GetForeignUpperPaths 回撥函式中,該函式在每個上層關係(即每個掃描/連線後處理步驟)被呼叫,前提是查詢的所有基本關係都來自同一個 FDW。

PlanForeignModify58.2.4 節中描述的其他回撥函式的設計假設外部關係將以常規方式進行掃描,然後將單個行更新由本地 ModifyTable 計劃節點驅動。這種方法對於需要讀取本地表和外部表的一般情況是必要的。但是,如果操作可以完全由外部伺服器執行,FDW 可以生成代表該操作的路徑並將其插入到 UPPERREL_FINAL 上層關係中,在那裡它將與 ModifyTable 方法競爭。這種方法也可以用於實現遠端 SELECT FOR UPDATE,而不是使用 58.2.6 節中描述的行鎖定回撥。請記住,插入到 UPPERREL_FINAL 的路徑負責實現查詢的所有行為。

在規劃 UPDATEDELETE 時,PlanForeignModifyPlanDirectModify 可以查詢外部表的 RelOptInfo 結構,並利用掃描規劃函式之前建立的 baserel->fdw_private 資料。但是,在 INSERT 中,目標表不會被掃描,因此沒有它的 RelOptInfoPlanForeignModify 返回的 ListForeignScan 計劃節點的 fdw_private 列表具有相同的限制,即它只能包含 copyObject 知道如何複製的結構。

INSERT 語句加上 ON CONFLICT 子句不支援指定衝突目標,因為遠端表上的唯一約束或排除約束在本地是未知的。這反過來也意味著 ON CONFLICT DO UPDATE 不被支援,因為在那裡必須指定衝突目標。

提交更正

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