PostgreSQLは、SQLの一意性制約を一意性インデックスを使用して強制します。
このインデックスでは、同一キーに対し複数の項目を許しません。
この機能をサポートするアクセスメソッドはamcanunique
を真に設定します。
(現時点ではb-treeのみがこれをサポートします。)
INCLUDE
句内の列のリストは、一意性制約の強制時には考慮されません。
MVCCのため、インデックス内に物理的に重複した項目が存在できることが常に必要です。 これらの項目は1つの論理的な行の連続的なバージョンを示します。 実際に強制させたい動作は、MVCCスナップショットが同じインデックスキーを持つ行を2つ含めないことです。 一意性インデックスに新しい行を挿入する時に検査しなければならない状況を以下のように分割することができます。
競合する有効な行が現在のトランザクションで削除された場合は問題ありません。 (具体的には、UPDATEは常に新しいバージョンを挿入する前に古い行バージョンを削除します。 これによりキーを変更することなく行をUPDATEすることができます。)
競合する行が未コミットのトランザクションで挿入された場合、挿入しようとしている方はトランザクションのコミットが分かるまで待機しなければなりません。 ロールバックした場合は競合しません。 競合する行が削除されずにコミットした場合、一意性違反となります。 (具体的には、他のトランザクションの終了をただ待機し、終了後に可視性の検査を完全に再実行します。)
同様に、競合する有効な行が未コミットのトランザクションで削除された場合、挿入しようとしている方はトランザクションのコミットまたはアボートを待機しなければならず、その後、試験を繰り返します。
さらに、上記規則に従った一意性違反を報告する直前に、アクセスメソッドは挿入される行の有効性を再度検査しなければなりません。
もし、無効なコミットであれば、違反を報告してはいけません。
(現在のトランザクションによって作成された通常の行の挿入という状況では、これは発生することはありません。
しかし、これはCREATE UNIQUE INDEX CONCURRENTLY
中に発生することがあります。)
インデックスアクセスメソッドにこうした試験を自身で行うことを要求します。 これは、インデックスの内容に対して重複するキーを持つことを示している任意の行のコミット状態を検査するために、ヒープまでアクセスしなければならないことを意味します。 これが醜くモジュール化されないことには疑う余地はありません。 しかし、余計な作業を防ぐことができます。 もし分離された探査を行ったとすると、新しいインデックス項目を挿入する場所を検索する時、競合する行に対するインデックス検索がどうしても繰り返されます。 さらに、競合検査がインデックス行の挿入部分で統合されて行われない限り、競合状態を防ぐ明確な方法がありません。
一意性制約が遅延可能である場合はさらに複雑になります。
新しい行向けのインデックス項目を挿入可能にする必要があります。
しかし一意性違反エラーは文の終わりまたはそれ以降まで遅延されます。
不要なインデックス検索の繰り返しを防ぐために、インデックスアクセスメソッドは初期の挿入の間に前座の一意性検査を行わなければなりません。
これが現存するタプルとまったく競合がないことを示した場合、それで終了です。
さもなければ、制約を強制する時に再検査を行うようスケジュールします。
再検査の時点で対象のタプルと同じキーを持つ何らかの他のタプルが存在すると、エラーを報告しなければなりません。
(この目的のために「存在する」は実際には「インデックス項目のHOTチェイン内に何らかのタプルが存在する」ことを意味します。)
これを実装するために、aminsert
は以下のいずれかの値を持つcheckUnique
パラメータを渡されます。
UNIQUE_CHECK_NO
は、一意性検査を行うことはない(これは一意性インデックスではない)ことを示します。
UNIQUE_CHECK_YES
は、上述の通り遅延がない一意性インデックスであり、一意性検査を即時に行わなければならないことを示します。
UNIQUE_CHECK_PARTIAL
は一意性制約が遅延可能であることを示します。
PostgreSQLはこのモードを使用して、各行のインデックス項目を挿入します。
このアクセスメソッドはインデックス内の重複する項目を許さなければなりません。
そしてaminsert
から偽を返すことで重複の可能性があることを報告しなければなりません。
偽が返された行それぞれに対して、遅延再検査が予定されます。
アクセスメソッドは一意性制約違反となるかもしれない行を識別しなければなりません。 しかし間違った偽を報告することはエラーではありません。 これにより他のトランザクションを待つことなく検査を行うことができます。 ここで報告された重複はエラーとして扱われず、後で再検査されます。 再検査時には重複しなくなっている可能性があります。
UNIQUE_CHECK_EXISTING
は、一意性違反の可能性があると報告された行に対する遅延再検査であることを示します。
これはaminsert
を呼び出すことで実装されますが、アクセスメソッドはこの場合に新しいインデックス項目を挿入してはいけません。
インデックス項目はすでに存在します。
それよりも、アクセスメソッドは他に存在するインデックス項目があるか検査する必要があります。
もし存在し、対象の行もまだ存在する場合エラーを報告します。
UNIQUE_CHECK_EXISTING
呼び出しでは、アクセスメソッドはさらに対象行が実際にインデックス内に既存の項目を持つか検証し、もしなければエラーを報告することを推奨します。
aminsert
に渡されるインデックスタプル値が再計算されているため勧めます。
インデックス定義に実際には不変ではない関数が含まれる場合、インデックスの間違った領域を検査してしまうかもしれません。
再検査にて対象行の存在を検査することで、元の挿入で使用されたものと同じタプル値をスキャンしていることを検証します。