他のバージョンの文書 15 | 14 | 13 | 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

57.5. 外部データラッパでの行ロック

FDWの元になる記憶機構が、行の同時更新を防ぐために個々の行をロックするという概念を持っているなら、PostgreSQLの通常のテーブルで使われている意味にできる限り現実的で近い行単位のロックをFDWが実施することは価値があるでしょう。 これに関していくつかの考慮点があります。

なされるべき重要な決定の一つは、早いロックを実行するか遅いロックを実行するか、です。 早いロックでは、行は、元となる記憶機構から最初に取り出されたときにロックされます。 一方、遅いロックでは、行は、それがロックされる必要があることがわかってからロックされます。 (この違いは、一部の行がローカルで検査される制約や結合条件によって除外されるために発生します。) 早いロックの方がずっと単純ですし、リモートの記憶機構との間の余分なやりとりもなくて済みますが、ロックしなくても良い行をロックするかもしれませんし、結果的に同時実行性が低下したり、予期しないデッドロックさえ発生します。 一方で、遅いロックは、ロックすべき行が後で一意に再識別できる場合にのみ可能です。 できれば、PostgreSQLのTIDがそうしているように、行識別子は行の特定のバージョンを識別できるのが望ましいです。

デフォルトではPostgreSQLはFDWとのやりとりにおいてロックの考慮をしませんが、FDWはコアのコードからの明示的なサポートなしに、早いロックを実行することができます。 PostgreSQLバージョン9.5で追加された57.2.6に記載されたAPI関数を使うことで、望むならFDWで遅いロックを使うことも可能です。

さらなる考慮点は、READ COMMITTED分離モードにおいて、PostgreSQLは対象のタプルの更新されたバージョンに対して制約と結合条件の再検査を行う必要があるかもしれないということです。 結合条件を再検査するには、前回取得対象のタプルと結合された、取得対象外の行の複製を再取得する必要があります。 PostgreSQLの標準テーブルを使うときは、結合を通じて生成される列リストに対象でないテーブルのTIDを含めて、必要な時には対象でない行を再フェッチすることで解決しています。 この方法は結合のデータセットを小さくできますが、安価な再フェッチ機能と再フェッチすべきバージョンの行を一意に特定できるTIDが必要になります。 そのためデフォルトで外部テーブルに対して使われる方法は、外部テーブルからフェッチされた行全体を結合を通じて生成した列リストに含めるというものです。 これによりFDWに対する特別な要請はなくなりますが、マージ結合およびハッシュ結合に置いてパフォーマンスが低下する結果となるかもしれません。 再フェッチの要求を満たすことができるFDWでは最初の方法を選択するのも良いでしょう。

外部テーブルに対するUPDATEDELETEでは、対象テーブルに対するForeignScan操作はフェッチする行を、恐らくはSELECT FOR UPDATEと同等なものを用いてロックすることが推奨されます。 FDWはテーブルがUPDATEまたはDELETEの対象かどうかを、計画時にそのrelidをroot->parse->resultRelationと比較することで、あるいは実行時にExecRelationIsTargetRelation()を使うことで検知できます。 これに代わる可能性として、ExecForeignUpdateまたはExecForeignDeleteのコールバック内で遅いロックを実行することがありますが、これについて特別なサポートは提供されません。

SELECT FOR UPDATE/SHAREコマンドによりロックすることが指定された外部テーブルについて、ForeignScanの操作ではSELECT FOR UPDATE/SHAREと同等なものを使ってタプルをフェッチすることで、ここでも早いロックを実行できます。 逆に遅いロックを実行するには、57.2.6で定義されるコールバック関数を提供して下さい。 GetForeignRowMarkTypeでは、要求されたロックの強度に応じて、rowmarkのオプションROW_MARK_EXCLUSIVEROW_MARK_NOKEYEXCLUSIVEROW_MARK_SHAREまたはROW_MARK_KEYSHAREを選択して下さい。 (コアのコードは、この4つのオプションのどれが選ばれたかに関係なく、同じ動作をします。) その他には、この種のコマンドによって外部テーブルのロックが指定されたかどうかを、計画時にget_plan_rowmarkを使うことで、あるいは実行時にExecFindRowMarkを使うことで検知できます。 このとき、NULLでないrowmark構造体が戻されるかどうかだけでなく、そのstrengthフィールドがLCS_NONEでないことも確認しなければなりません。

最後に、UPDATEDELETEまたはSELECT FOR UPDATE/SHAREコマンドで使用されたが、行ロックの指定はされなかった外部テーブルについて、ロック強度がLCS_NONEになっているときにGetForeignRowMarkTypeでオプションROW_MARK_REFERENCEを選択すれば、すべての行を複製するというデフォルトの動作を変更することができます。 これにより、markTypeにその値を入れてRefetchForeignRowが呼び出されるようになります。 このとき、新しいロックを取得することなく行を再取得します。 (GetForeignRowMarkType関数を使うが、ロックしていない行を再フェッチしたくない場合は、LCS_NONEについてオプションROW_MARK_COPYを選択して下さい。)

さらなる情報は、src/include/nodes/lockoptions.hsrc/include/nodes/plannodes.hでのRowMarkTypePlanRowMarkについてのコメント、src/include/nodes/execnodes.hでのExecRowMarkについてのコメントを参照して下さい。