PostgreSQL 外部數(shù)據(jù)包裝器中的行鎖定

2021-08-24 14:37 更新

如果一個(gè) FDW 的底層存儲(chǔ)機(jī)制具有鎖定行的概念來(lái)阻止對(duì)行的并發(fā)更新,通常值得 FDW 去執(zhí)行行級(jí)鎖定以盡可能接近在普通PostgreSQL表中所實(shí)際使用的語(yǔ)義。涉及這個(gè)問(wèn)題有多種考慮。

要做出的一個(gè)關(guān)鍵決定是執(zhí)行早期鎖定還是晚期鎖定。在早期鎖定中,當(dāng)一行被第一次從底層存儲(chǔ)中檢索到時(shí),它會(huì)被鎖定;而在晚期鎖定中,只有當(dāng)行需要被鎖定時(shí)才鎖定它(由于某些行可能被本地檢查的限制或者連接條件拋棄,所以會(huì)出現(xiàn)不同)。早期鎖定更加簡(jiǎn)單并且能避免額外地與遠(yuǎn)程存儲(chǔ)交互,但是可能會(huì)導(dǎo)致一些不需要鎖定的行也被鎖定,最終造成并發(fā)性下降甚至意外的死鎖。還有,只有在要被鎖定的行可以在后期唯一地重新標(biāo)識(shí)時(shí)才可以用晚期鎖定。較好的行標(biāo)識(shí)符應(yīng)該能標(biāo)識(shí)行的特定版本,就像 PostgreSQLTID 那樣。

默認(rèn)情況下,PostgreSQL在與 FDW 交互時(shí)會(huì)忽略鎖定考慮,但是 FDW 可以在沒(méi)有核心代碼顯式支持的情況下執(zhí)行早期鎖定。第 56.2.5 節(jié)中描述的 API 函數(shù)(在PostgreSQL 9.5 中加入)允許 FDW 按照意愿使用晚期鎖定。

一個(gè)額外的考慮是在READ COMMITTED隔離模式中,PostgreSQL可能需要對(duì)某個(gè)目標(biāo)元組的更新版本進(jìn)行限制以及連接條件的重新檢查。重新檢查連接條件要求重新獲得之前連接成目標(biāo)元組的非目標(biāo)行拷貝。在標(biāo)準(zhǔn)PostgreSQL表的情況下,這可以通過(guò)在連接投影出的列列表中包括非目標(biāo)表的 TID 并且在需要時(shí)重新取得非目標(biāo)行來(lái)做到。這種方法可以讓連接數(shù)據(jù)集保持緊湊,但是它要求代價(jià)較低的重新取得元組的功能,還有 TID 要能夠唯一地標(biāo)識(shí)要被重新取得的行版本。因此,默認(rèn)情況下用于外部表的方法是將整個(gè)外部表元組的拷貝包括在從連接投影出的列列表中。這不會(huì)對(duì) FDW 有特殊的要求,但是會(huì)導(dǎo)致歸并和哈希連接性能下降。要滿足重新取得元組需求的 FDW 可以選擇第一種方式。

對(duì)于在外部表上的UPDATE或者DELETE,推薦目標(biāo)表上的ForeignScan操作在它取得的行上執(zhí)行早期鎖定(可能通過(guò)SELECT FOR UPDATE的等效體)。通過(guò)在規(guī)劃時(shí)比較一個(gè)表的 relid 和root->parse->resultRelation或在執(zhí)行時(shí)使用 ExecRelationIsTargetRelation(),一個(gè) FDW 可以檢測(cè)該表是否為UPDATE/DELETE的目標(biāo)。另一種可能性是在ExecForeignUpdate或者ExecForeignDelete回調(diào)中執(zhí)行晚期鎖定,但是對(duì)此沒(méi)有特別的支持。

對(duì)于通過(guò)SELECT FOR UPDATE/SHARE命令指定要被鎖定的外部表,ForeignScan操作同樣可以通過(guò)用SELECT FOR UPDATE/SHARE的等效體取元組來(lái)執(zhí)行早期鎖定。要執(zhí)行晚期鎖定,請(qǐng)?zhí)峁?a class="xref" href="fdw-callbacks.html#FDW-CALLBACKS-ROW-LOCKING" title="56.2.5. 用于行鎖定的 FDW 例程">第 56.2.5 節(jié)中定義的回調(diào)函數(shù)。在GetForeignRowMarkType中,根據(jù)請(qǐng)求的鎖長(zhǎng)度來(lái)選擇行標(biāo)記選項(xiàng)ROW_MARK_EXCLUSIVE、ROW_MARK_NOKEYEXCLUSIVE、ROW_MARK_SHARE或者 ROW_MARK_KEYSHARE(不管選擇哪一種選項(xiàng),核心代碼都會(huì)做同樣的事情)。在別的地方,可以在規(guī)劃時(shí)用get_plan_rowmark或者在執(zhí)行時(shí)用ExecFindRowMark來(lái)檢測(cè)一個(gè)外部表是否被指定由這種類型的命令鎖定。你必須不僅僅檢測(cè)是否返回了一個(gè)非空的行標(biāo)記結(jié)構(gòu),還要檢測(cè)它的strength域不是 LCS_NONE

最后,對(duì)于在UPDATE、DELETE或者SELECT FOR UPDATE/SHARE命令中使用但是沒(méi)有被指定要行鎖定的外部表,你可以在看到鎖長(zhǎng)度LCS_NONE時(shí)通過(guò)使用GetForeignRowMarkType選擇選項(xiàng) ROW_MARK_REFERENCE來(lái)把默認(rèn)選擇覆蓋為拷貝整個(gè)行。 這將導(dǎo)致用那個(gè)值作為markType來(lái)調(diào)用RefetchForeignRow。它應(yīng)該接著重新取得該行而不獲取任何新鎖(如果你有一個(gè)GetForeignRowMarkType函數(shù),但是不想重新取未鎖定的行,可為LCS_NONE選擇選項(xiàng) ROW_MARK_COPY)。

更多信息可見(jiàn)src/include/nodes/lockoptions.h,以及src/include/nodes/plannodes.hRowMarkTypePlanRowMark的注釋,還有src/include/nodes/execnodes.hExecRowMark的注釋。

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

掃描二維碼

下載編程獅App

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

編程獅公眾號(hào)