アプリケーションレベルでのデータの一貫性チェック

PostgreSQL 9.1.1文書
前のページ | 巻戻し | 第 13章同時実行制御 | 早送り | 次のページ

# 13.4. アプリケーションレベルでのデータの一貫性チェック

データの参照範囲は各ステートメントで変化するので、リードコミッティドトランザクションを使用して、データ保全性に関するビジネスルールを強化するのは非常に難しいことです。また、書き込み競合が生じる場合、単一のステートメントでさえステートメントのスナップショットに限定されないかもしれません。

リピータブルリードトランザクションは実行全体にわたってデータの安定した参照範囲を持ちますが、MVCCスナップショットをデータ完全性チェックに使用することによる、読み取り/書き込み競合として知られるものを含む、微妙な問題があります。 1つのトランザクションがデータを書き、同時に実行するトランザクションが、同じデータ(書き込みの前に、あるいはその書き込みの後にも)を読むことを試みる場合、それは別のトランザクションの働きを見ることができません。 その後、読み手は、どれが最初にスタートしたか、あるいは、どれが最初にコミットしたかにかかわらず最初に実行したように見えます。 そのままいけば問題はありませんが、読み手がさらにデータを書けば、どれが同時に実行したトランザクションがそれを読んだ場合、上で述べたトランザクションのどちらかの前に走ったように見えるトランザクションとなってしまいます。 最後に実行したように見えるトランザクションが実際には最初にコミットしていた場合、トランザクションの実行順のグラフには循環が容易に出現します。 そのような循環が出現する時、完全性のチェックはなにかしらの支援がなければ正しく動作しません。

項13.2.3により述べたように、シリアライザブルトランザクションは、危険なパターンの読み取り/書き込み競合のための非ブロッキング監視を加えたリピータブルリードトランザクションです。 明白に実行順が循環を引き起こすパターンが検知された場合、含まれていたトランザクションのうちの1つは循環を断ち切るためにロールバックされます。

## 13.4.1. シリアライザブルトランザクションを用いた一貫性の強化

シリアライザブルトランザクション分離レベルが、データの一貫性を必要とするすべての書き込みおよびすべての読み取りに使用される場合、一貫性を確実にするために必要なことは他にはありません。 一貫性を保証するためにシリアライザブルトランザクションを使用するよう書かれている他の環境からのソフトウェアは、PostgreSQLでこの点に関して"正しく動く"べきです。

この技術を使用した場合、アプリケーションソフトウェアが直列化失敗でロールバックしたトランザクションを自動的に再試行するようなフレームワークを備えている場合、アプリケーションプログラマにとって不必要な負担を生み出さないようにするでしょう。 `default_transaction_isolation`を`serializable`にセットすることはよい考えかもしれません。 他のトランザクション分離レベルは使用されないことを保証する処置を講ずる、そうでなければ、不注意に完全位チェックを失わないよう、トリガーでトランザクション分離レベルのチェックをすることも賢明でしょう。

実行に関する提言は項13.2.3を参照してください。

警告
シリアライザブルトランザクションを使用する整合性保護レベルは、まだホットスタンバイモード(項25.5)には拡張されていません。そのために、ホットスタンバイを使用する場合は、マスタにおけるリピータブルリードと明示的なロック処理の利用が望まれるかもしれません。

## 13.4.2. 明示的なブロッキングロックを用いた一貫性の強化

非シリアライザブルの書き込みが可能な場合、 ある行の現時点の有効性を確実なものとし、同時更新を避けるためには、`SELECT FOR UPDATE`文や`SELECT FOR SHARE`文、適切な`LOCK TABLE`文を使用する必要があります （`SELECT FOR UPDATE`文および`SELECT FOR SHARE`文は返ってきた行のみを同時に起こる更新からロックし、`LOCK TABLE`はテーブル全体をロックします）。 これはPostgreSQLに他の環境からアプリケーションを移植する時に考慮されなければなりません

他の環境から切り替えた場合のさらなる注意点としては、同時実行トランザクションが選択された行を更新しないか削除しないということを`SELECT FOR UPDATE`が保証しないという事実です。 PostgreSQLでそれをするためには、値を変更する必要がなくても、実際に行を更新しなければなりません。 `SELECT FOR UPDATE`は、他のトランザクションが同じロックを獲得すること、または、ロックされた行に影響する`UPDATE`または`DELETE`を実行することを一時的にブロックします。 しかしトランザクションがコミットするかロールバックして一度このロックを獲得すると、ロックが獲得されている間に、行の実際の`UPDATE`が行なわれなかった場合、ブロックされたトランザクションは、競合した操作を続けることになります。

MVCCにおいては全体的な有効性チェックに特別な考慮を払わなければなりません。 例えば銀行のアプリケーションで、１つのテーブルにある全ての貸方の合計が、別のテーブルにある借方の合計と同じであることを、二つのテーブルが常に更新されているときに、チェックする必要があるとします。 2つの連続する`SELECT SUM(...)`コマンドの結果を比べると、2番目の問い合わせは、おそらく最初の問い合わせによってカウントされなかったトランザクションの結果を含んでいるため、リードコミッティドモードでは信頼のおける処理を実行できないことがわかります。 1つのシリアライザブルトランザクションで2つの合計を出力すると、シリアライザブルトランザクションが開始される前にコミットされたトランザクション結果のみの正確な状況を得ることができます。 しかし、その結果がもたらされた時点でもなお妥当であるかどうかは、実際には疑わしいかもしれません。 整合性チェックを行う前にシリアライザブルトランザクション自身が変更を行った場合、そのチェックの有効性はさらに疑わしくなります。 これにより、トランザクション開始後に行われる変更の全てだけでなく、何か別のものが含まれるためです。 このような場合、注意深い人であれば、現状を確実に把握するためにチェックに必要な全てのテーブルをロックするでしょう。 `SHARE`モード（もしくはそれ以上）のロックにより、現在のトランザクションでの変更を除き、ロックされたテーブルにコミットされていない変更が存在しないことが保証されます。

同時に、明示的なロック処理を使用して、同時に変更が実行されるのを防ごうとする場合、リードコミッティドモードの使用または、リピータブルリードモードを使用する場合は、問い合わせを実行する前にロックを獲得するよう留意してください。 もしくは、シリアライザブルモードを使用する場合は、問い合わせを実行する前にロックを獲得するよう留意してください。 リピータブルリードトランザクションにおいて獲得されたロックは、テーブルに変更をかける他のトランザクションが現在実行されていないことを保証します。 しかし、トランザクションが参照しているスナップショットが、ロックの獲得より前に取得されたものであれば、そのスナップショットは現時点においてコミットされている変更より前のテーブルのものである可能性があります。 リピータブルリードトランザクションのスナップショットは、実際にはその最初の問い合わせもしくはデータ変更コマンド（`SELECT`、`INSERT`、`UPDATE`、または`DELETE`）が開始された時点で取得されます。 したがって、スナップショットを取得する前に、明示的にロックを獲得することが可能です。

前のページ | ホーム | 次のページ
明示的ロック | 上に戻る | ロックとインデックス