インデックスアクセスメソッドは、複数のプロセスによるインデックスの同時更新を取り扱えなければなりません。
PostgreSQLコアシステムはインデックススキャン中にインデックスに対してAccessShareLock
を獲得します。
また、(通常のVACUUM
を含む)インデックスの更新中にRowExclusiveLock
を獲得します。
これらの種類のロックは競合しませんので、アクセスメソッドは必要になるかもしれない粒度の細かなロック処理に関して責任を持ちます。
インデックスの生成、破棄、REINDEX
時にインデックス全体に対する排他ロックが獲得されます。
同時更新をサポートするインデックス種類を構築することは通常、必要な動作について広範かつ微細にわたる解析が必要です。
B-treeおよびハッシュインデックス種類では、src/backend/access/nbtree/README
と src/backend/access/hash/README
にある設計に関する決定事項を読むことができます。
インデックス自身の内部的な一貫性要求の他に、同時実行更新には、親テーブル(ヒープ)とインデックス間の一貫性に関する問題が発生します。 PostgreSQLはヒープへのアクセスおよび更新とインデックスへのアクセスおよび更新を分離していますので、インデックスとヒープとの間の一貫性が無くなる間隔が存在します。 以下の規則でこうした問題を扱います。
新しいヒープ項目はインデックス項目を作成する前に作成されます。 (このため、同時実行インデックススキャンはヒープエントリを確認する時によく失敗します。 インデックスの読み取りは、未コミットの行を対象としませんので問題ありません。 しかし、61.5を参照してください。)
ヒープエントリが(VACUUM
によって)削除される時、これに対するすべてのインデックス項目が先に削除されます。
インデックススキャンは、最後にamgettuple
が返した項目を保持するインデックスページ上のピンを管理しなければなりません。
また、ambulkdelete
は、他のバックエンドがピンを持つページから項目を削除することはできません。
この規則の必要性については後で説明します。
3番目の規則がないと、VACUUM
によって削除される直前に、インデックス読み取りがインデックス項目を見つけ、そして、VACUUM
によって削除された後に対応するヒープ項目に達する可能性があります。
空の項目スロットはheap_fetch()
で無視されますので、これは読み取りが達した時にその項目番号が未使用である場合でも大きな問題は起こりません。
しかし、第三のバックエンドがすでにその項目スロットを他のものに再使用した場合はどうなるでしょうか?
そのスロット内の新しいものが、スナップショット試験を通過するには新しすぎることが確実ですので、MVCCに則ったスナップショットを使用する場合は問題ありません。
しかし、MVCCに則らないスナップショット(SnapshotNow
など)では、実際にはスキャンキーに合わない行を受付け、返す可能性があります。
すべての場合においてヒープ行に対しスキャンキーの再検査を行うことを必須とすることで、こうした状況から保護することができますが、これは高価すぎます。
代わりに、読み取りがまだ一致するヒープ項目へのインデックス項目の「作業中」であることを示す代理として、インデックスページに対するピンを使用します。
このピンに対してambulkdelete
がブロックするようにすることで、読み取りの作業が終わる前にVACUUM
がそのヒープ項目を削除できないことを確実にします。
実行時におけるこの対策のコストは小さく、実際に競合が発生するごく稀な場合にのみブロックするためのオーバーヘッドが加わります。
この対策は、インデックススキャンが「同期」していることを要求します。 対応するインデックス項目のスキャンの後即座に各ヒープタプルを取り出さなければなりません。 多くの理由のため、これは高価です。 インデックスから多くのTIDを収集し、少し後でのみヒープタプルにアクセスする「非同期」スキャンでは、必要なロック処理オーバーヘッドがかなり少なくなり、また、より効率的なヒープへのアクセスパターンを取ることができます。 上の解析に従うと、MVCCに則らないスナップショットでは同期方式を使用しなければなりませんが、問い合わせがMVCCスナップショットを使用する場合は非同期スキャンを使用することができます。
amgetbitmap
インデックススキャンでは、アクセスメソッドは返されるタプル上にインデックスピンをまったく保持しません。
したがって、MVCCに則ったスナップショットでこうしたスキャンを使用することのみが安全です。
ampredlocks
フラグが設定されていない場合、シリアライザブルトランザクション内でそのインデックスアクセスメソッドを使用するスキャンはいずれもインデックス全体に対するブロックしない述語ロックを獲得します。
これは、同時実行のシリアライザブルトランザクションによるそのインデックスへの何らかのタプル挿入で、読み書きの競合が発生することがあります。
同時実行のシリアライザブルトランザクションの集合の中で特定の読み書きの競合パターンが検知された場合、データの整合性を保護するためにこれらのトランザクションの1つはキャンセルされます。
このフラグが設定されている場合、こうしたトランザクションのキャンセルの頻度を低減することになる、より粒度の細かな述語ロックをインデックスアクセスメソッドが実装していることを示します。