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