PostgreSQL 外部數(shù)據(jù)包裝器查詢規(guī)劃

2021-08-24 14:36 更新

FDW回調(diào)函數(shù)GetForeignRelSize、GetForeignPaths、GetForeignPlan、PlanForeignModify、GetForeignJoinPaths、 GetForeignUpperPaths以及PlanDirectModify必須適合PostgreSQL規(guī)劃器的工作。這里有一些關(guān)于它們必須做什么的注記。

rootbaserel中的信息可以被用來(lái)減少必須從外部表獲得的信息量(并且因此降低代價(jià))。baserel->baserestrictinfo是特別有趣的,因?yàn)樗拗茥l件(WHERE)子句,它應(yīng)該被用來(lái)過(guò)濾要被獲取的行(FDW本身并不要求強(qiáng)制這些條件,因?yàn)楹诵膱?zhí)行器可以檢查它們)。 baserel->reltarget->exprs可以被用來(lái)決定哪些類需要被獲??;但是注意它僅列出了ForeignScan計(jì)劃節(jié)點(diǎn)所發(fā)出的列,不包含在條件計(jì)算中使用但并不被查詢輸出的列。

有多個(gè)私有域可以給FDW規(guī)劃函數(shù)來(lái)保存信息。通常,不管你存儲(chǔ)什么在FDW私有域中,它們都應(yīng)該被palloc,這樣它會(huì)在規(guī)劃結(jié)束時(shí)被回收。

baserel->fdw_private是一個(gè)void指針,它可以被FDW規(guī)劃函數(shù)用來(lái)存儲(chǔ)與特定外部表相關(guān)的信息。核心規(guī)劃器不會(huì)碰它除非當(dāng)RelOptInfo節(jié)點(diǎn)被創(chuàng)建時(shí)把它初始化為NULL。它對(duì)從GetForeignRelSize傳遞信息給GetForeignPaths和/或從 GetForeignPaths傳遞信息給GetForeignPlan非常有用,這樣避免了重新計(jì)算。

GetForeignPaths可以通過(guò)在ForeignPath節(jié)點(diǎn)的fdw_private域中存儲(chǔ)私有信息來(lái)標(biāo)識(shí)不同的訪問(wèn)路徑。fdw_private被聲明為一個(gè)List指針,但是可能實(shí)際上包含任何東西,因?yàn)橐?guī)劃器不會(huì)觸碰它。但是,最好是使用一種 nodeToString可導(dǎo)出的形式,這樣在后端可以用于調(diào)試支持。

GetForeignPlan可以檢查選中的ForeignPath節(jié)點(diǎn)的fdw_private域,并且可以生成被放置于ForeignPath計(jì)劃節(jié)點(diǎn)中的fdw_exprsfdw_private列表。這兩個(gè)列表必須被表示為一種copyObject可復(fù)制的形式。fdw_private列表沒(méi)有任何其他限制并且不會(huì)被核心后端以任何形式解釋。非 NIL 的fdw_exprs應(yīng)該包含表達(dá)式樹(shù),該樹(shù)會(huì)在運(yùn)行時(shí)被執(zhí)行。這些樹(shù)將由規(guī)劃器在后期處理,以便讓它們變成完全可執(zhí)行的。

GetForeignPlan中,通常被傳入的目標(biāo)列表可以被照樣復(fù)制到計(jì)劃節(jié)點(diǎn)中。被傳入的scan_clauses 列表包含和baserel->baserestrictinfo相同的子句,但是可能為了更好的執(zhí)行效率會(huì)被重新排序。在簡(jiǎn)單情況下,F(xiàn)DW可以只把RestrictInfo節(jié)點(diǎn)從 scan_clauses列表剝離(使用extract_actual_clauses)并且把所有子句放到計(jì)劃節(jié)點(diǎn)的條件列表中,這意味著所有子句將在運(yùn)行時(shí)由執(zhí)行器檢查。更復(fù)雜的FDW可能可以在內(nèi)部檢查某些子句,著這種情況下哪些子句可以從計(jì)劃節(jié)點(diǎn)的條件列表中刪除,這樣執(zhí)行器就不用浪費(fèi)時(shí)間去檢查它們。

作為一個(gè)例子,F(xiàn)DW可以標(biāo)識(shí)某些foreign_variable = sub_expression形式的限制子句,它決定哪些可以使用由sub_expression給出的本地計(jì)算值在遠(yuǎn)程服務(wù)器上被執(zhí)行。這樣一個(gè)子句的實(shí)際標(biāo)識(shí)應(yīng)該在 GetForeignPaths期間發(fā)生,因?yàn)樗赡軙?huì)影響路徑的代價(jià)估計(jì)。路徑的fdw_private域可能包括一個(gè)已標(biāo)識(shí)的子句的RestrictInfo節(jié)點(diǎn)。然后GetForeignPlan將從scan_clauses 中移除該子句,但是將 sub_expression 加到fdw_exprs來(lái)保證它被揉成可執(zhí)行的形式。它可能還將把控制信息放入到計(jì)劃節(jié)點(diǎn)的fdw_private域來(lái)告訴執(zhí)行函數(shù)在運(yùn)行時(shí)要做什么。傳遞給遠(yuǎn)程服務(wù)器的查詢將涉及類似WHERE foreign_variable = $1的東西,使用在運(yùn)行時(shí)從fdw_exprs表達(dá)式樹(shù)獲得的參數(shù)值。

任何從該計(jì)劃節(jié)點(diǎn)的條件列表移除的子句必須被加入到fdw_recheck_quals或者由RecheckForeignScan重新檢查以便確保在READ COMMITTED隔離級(jí)別的正確行為。當(dāng)查詢中涉及的某個(gè)其他表上發(fā)生并發(fā)更新時(shí),執(zhí)行器可能需要驗(yàn)證原來(lái)的所有條件仍然對(duì)該元組滿足(可能用一組不同的參數(shù)值)。使用fdw_recheck_quals通常比在 RecheckForeignScan中實(shí)現(xiàn)檢查要更容易,但是這種方法不足以應(yīng)付外連接被下推的情況,因?yàn)槟欠N情況下的連接元組可能會(huì)有一些域具有 NULL 但是不會(huì)導(dǎo)致整個(gè)元組被拒絕。

另一個(gè)可以由 FDW 填充的ForeignScan域是fdw_scan_tlist,它描述 FDW 為這個(gè)計(jì)劃節(jié)點(diǎn)返回的元組。對(duì)于簡(jiǎn)單的外部表掃描這可以設(shè)置為NIL,表示返回的元組具有為外部表聲明的行類型。非-NIL值必須是一個(gè)包含表示返回列的 Var 或表達(dá)式的目標(biāo)列表( TargetEntry的列表)。例如,這可以被用來(lái)顯示 FDW 省略了某些查詢不需要的列。還有,如果 FDW 計(jì)算表達(dá)式比在本地計(jì)算代價(jià)更低,可以把那些表達(dá)式加入到fdw_scan_tlist。注意連接計(jì)劃(從GetForeignJoinPaths創(chuàng)建的路徑得到)必須總是提供fdw_scan_tlist來(lái)描述它們將返回的列集合。

FDW應(yīng)該總是只依靠表的限制子句構(gòu)建至少一個(gè)路徑。在連接查詢中,它可能還會(huì)選擇依靠連接子句構(gòu)建路徑,例如foreign_variable = local_variable。這樣的子句將不會(huì)在baserel->baserestrictinfo中找到,但是必須出現(xiàn)在關(guān)系的連接列表中。使用這樣一個(gè)子句的路徑被稱為一個(gè) 參數(shù)化路徑。它必須用一個(gè)合適的param_info值來(lái)標(biāo)識(shí)其他被使用在選中的連接子句中的關(guān)系;使用get_baserel_parampathinfo來(lái)計(jì)算該值。在GetForeignPlan中,連接子句的local_variable部分將被加到 fdw_exprs中,并且接著在運(yùn)行時(shí)和一個(gè)普通限制子句一樣工作。

如果一個(gè) FDW 支持遠(yuǎn)程連接,GetForeignJoinPaths應(yīng)該和GetForeignPaths對(duì)基本表所作的那樣為潛在的遠(yuǎn)程連接產(chǎn)生ForeignPath。有關(guān)想要進(jìn)行的連接的信息可以以上述相同的方式傳遞給GetForeignPlan。不過(guò), baserestrictinfo與連接關(guān)系無(wú)關(guān),一個(gè)特定連接的相關(guān)連接子句將被作為一個(gè)獨(dú)立的參數(shù)(extra->restrictlist)被傳遞給GetForeignJoinPaths。

FDW 可能會(huì)額外地支持直接執(zhí)行某些在掃描和連接層次之上的計(jì)劃動(dòng)作,例如分組或者聚集。為了提供這類選項(xiàng),F(xiàn)DW 應(yīng)該生成路徑并且把它們插入到合適的上層關(guān)系中。例如,一條表示遠(yuǎn)程聚集的路徑應(yīng)該被使用add_path插入到UPPERREL_GROUP_AGG關(guān)系中。這條路徑的代價(jià)將會(huì)與通過(guò)讀取外部關(guān)系的簡(jiǎn)單掃描路徑的本地聚集(注意這樣一條路徑也必須被提供,否則規(guī)劃時(shí)會(huì)有錯(cuò)誤)進(jìn)行比較。如果遠(yuǎn)程聚集路徑勝出(通常是這樣),它會(huì)被以通常的方式(調(diào)用 GetForeignPlan)轉(zhuǎn)化成計(jì)劃。如果該查詢的所有基本關(guān)系都來(lái)自于同一個(gè) FDW,推薦在GetForeignUpperPaths回調(diào)函數(shù)中生成這種路徑,該函數(shù)會(huì)為每一個(gè)上層關(guān)系被調(diào)用(即每一次掃描/連接后處理步驟)。

第 56.2.4 節(jié)中描述的PlanForeignModify以及其他回調(diào)的設(shè)計(jì)是建立在這樣一個(gè)假設(shè)之上:外部表將以通常的方式被掃描并且行更新將被一個(gè)本地ModifyTable計(jì)劃節(jié)點(diǎn)所驅(qū)動(dòng)。這種方法對(duì)于更新需要讀取本地表以及外部表的一般情況下是必要的。不過(guò),如果操作可以完全由外部服務(wù)器執(zhí)行,F(xiàn)DW 可以產(chǎn)生一個(gè)表示這種操作的計(jì)劃并且把它插入到UPPERREL_FINAL上層關(guān)系中,在其中它會(huì)與ModifyTable方法競(jìng)爭(zhēng)。這種方法還可以被用來(lái)實(shí)現(xiàn)遠(yuǎn)程SELECT FOR UPDATE,而不使用第 56.2.5 節(jié)中描述的行鎖定回調(diào)。記住插入到UPPERREL_FINAL中的路徑負(fù)責(zé)實(shí)現(xiàn)查詢的所有行為。

在規(guī)劃一個(gè)UPDATEDELETE時(shí),PlanForeignModifyPlanDirectModify能為外部表查找RelOptInfo結(jié)構(gòu),并利用之前由掃描規(guī)劃函數(shù)創(chuàng)建的baserel->fdw_private數(shù)據(jù)。但是,在 INSERT中目標(biāo)表不會(huì)被掃描,因此不會(huì)有它的RelOptInfo。由PlanForeignModify返回的List具有和ForeignScan計(jì)劃節(jié)點(diǎn)的fdw_private列表相同的限制,即它必須只包含 copyObject知道怎么拷貝的結(jié)構(gòu)。

帶有一個(gè)ON CONFLICT子句的INSERT不支持指定沖突目標(biāo),因?yàn)楸镜夭恢肋h(yuǎn)程表上的唯一約束和排除約束的情況。然后這也意味著ON CONFLICT DO UPDATE不被支持,因?yàn)樵撜f(shuō)明是強(qiáng)制性的。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)