明示的ロック

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

# 12.3. 明示的ロック

PostgreSQLは、テーブル内のデータに対する同時アクセスを制御するために様々な種類のロックモードを備えています。 これらのモードは、MVCCでは必要な動作を得られない場合、アプリケーション制御のロックに使用することができます。 また、ほとんどのPostgreSQLコマンドでは、参照されるテーブルがそのコマンドの実行中に別の方法で削除もしくは変更されていないことを確実にするために、適切なモードのロックを自動的に獲得します。 (例えば、`ALTER TABLE`コマンドは、同じテーブルに対する他の操作とは同時に実行できません。)

現在のデータベースサーバで重要なロックの一覧を確認するには、`pg_locks`システムビュー(項41.33)を使用してください。 ロック管理サブシステムの状況監視についての詳細は第23章を参照してください。

## 12.3.1. テーブルレベルロック

以下のリストに、PostgreSQLで自動的に使用される、使用可能なロックモードとその文脈を示します。 また、LOCKコマンドを使用して、こうしたロックを明示的に獲得することもできます。 これらのロックモードは、たとえその名前に"row(行)"という言葉がついていても、全てテーブルレベルのロックであることに注意してください。 ロックモードの名前は歴史的なものです。 これらの名前は、各ロックモードの代表的な使用方法をある程度表しています。 しかし、意味的には全て同じです。 ロックモード間における唯一の実質的な差異は、どのモードがどのモードと競合するかというロックモードの組合せです。 2つのトランザクションで、競合するモードのロックを同時に同一テーブル上に保持することはできません。 (しかし、トランザクションは自分自身とは決して競合しません。 例えば、`ACCESS EXCLUSIVE`ロックを獲得し、その後同じテーブルに`ACCESS SHARE`ロックを獲得することができます。) 競合しないロックモードは、多くのトランザクションで同時に保持することが可能です。 特に、ロックモードには、自己競合するもの (例えば、`ACCESS EXCLUSIVE`は同時に複数のトランザクションで保持することは不可能)と、自己競合しないもの (例えば、`ACCESS SHARE`は複数のトランザクションで保持可能)があることに注意してください。 一旦獲得されると、ロックモードはトランザクションが終了するまで維持されます。

テーブルレベルロックモード

`ACCESS SHARE`

  `ACCESS EXCLUSIVE` ロックモードとのみ競合します。

  `SELECT`と`ANALYZE`コマンドにより、参照されるテーブルに対してこのモードのロックが獲得されます。 通常、テーブルの読み取りのみで変更を行なわない問い合わせであれば全て、このロックモードを獲得します。

`ROW SHARE`

  `EXCLUSIVE`および`ACCESS EXCLUSIVE`ロックモードと競合します。

  `SELECT FOR UPDATE`コマンドは、(参照はされているが、`FOR UPDATE`として選択はされていない他のテーブルに対する`ACCESS SHARE`ロックに加えて)対象となるテーブル上にこのモードのロックを獲得します。

`ROW EXCLUSIVE`

  `SHARE`、`SHARE ROW EXCLUSIVE`、`EXCLUSIVE`、および`ACCESS EXCLUSIVE`ロックモードと競合します。

  `UPDATE`、`DELETE`、および`INSERT`コマンドは、(参照される他の全てのテーブルに対する`ACCESS SHARE`ロックに加えて)対象となるテーブル上にこのモードのロックを獲得します。 通常、このロックモードは、テーブルのデータを変更する問い合わせにより獲得されます。

`SHARE UPDATE EXCLUSIVE`

  `SHARE UPDATE EXCLUSIVE`、`SHARE`、`SHARE ROW EXCLUSIVE`、`EXCLUSIVE`、および`ACCESS EXCLUSIVE`ロックモードと競合します。 このモードにより、同時実行されるスキーマの変更および`VACUUM`コマンドの実行から、テーブルを保護します。

  (`FULL`無しの)`VACUUM`コマンドによって獲得されます。

`SHARE`

  `ROW EXCLUSIVE`、`SHARE UPDATE EXCLUSIVE`、`SHARE ROW EXCLUSIVE`、`EXCLUSIVE`、および`ACCESS EXCLUSIVE`ロックモードと競合します。 このモードにより、同時実行されるデータ変更からテーブルを保護します。

  `CREATE INDEX`によって獲得されます。

`SHARE ROW EXCLUSIVE`

  `ROW EXCLUSIVE`、`SHARE UPDATE EXCLUSIVE`、`SHARE`、`SHARE ROW EXCLUSIVE`、`EXCLUSIVE`、および`ACCESS EXCLUSIVE`ロックモードと競合します。

  このロックモードを自動的に獲得するPostgreSQLコマンドはありません。

`EXCLUSIVE`

  `ROW SHARE`、`ROW EXCLUSIVE`、`SHARE UPDATE EXCLUSIVE`、`SHARE`、`SHARE ROW EXCLUSIVE`、`EXCLUSIVE`、および `ACCESS EXCLUSIVE`ロックモードと競合します。 このモードは、同時実行される`ACCESS SHARE`のみを許可します。 つまり、このロックモードを保持するトランザクションと並行して実行できる処理は、テーブルの読み取りだけです。

  このロックモードを自動的に獲得するPostgreSQLコマンドはありません。

`ACCESS EXCLUSIVE`

  全てのロックモード(`ACCESS SHARE`、`ROW SHARE`、`ROW EXCLUSIVE`、`SHARE UPDATE EXCLUSIVE`、`SHARE`、`SHARE ROW EXCLUSIVE`、`EXCLUSIVE`、および`ACCESS EXCLUSIVE`)と競合します。 このモードにより、その保持者以外にテーブルにアクセスするトランザクションがないことが保証されます。

  `ALTER TABLE`、`DROP TABLE`、`REINDEX`、`CLUSTER`および`VACUUM FULL`コマンドによって獲得されます。 また、明示的にモードを指定しなければ、これが`LOCK TABLE`文を使用する際のデフォルトのロックモードです。

> ティップ: `ACCESS EXCLUSIVE`ロックのみが、`SELECT`(`FOR UPDATE`無し)文をブロックします。

## 12.3.2. 行レベルロック

テーブルレベルロックに加えて、行レベルのロックもあります。 指定された行に対する行レベルロックは、行が更新(または削除、または更新対象としてマーク)されると、自動的に獲得されます。 このロックは、トランザクションがコミットまたはロールバックするまで保持されます。 行レベルロックは、データの問い合わせには影響を与えません。 行レベルロックは、同じ行に対する書き込みのみをブロックします。 実際に行を変更せずに行に対して行レベルロックを獲得するには、該当する行を`SELECT FOR UPDATE`で選択してください。 いったん特定の行レベルロックが獲得されると、競合を心配しないで、そのトランザクション内では何回でも行の変更が可能であることを覚えておいてください。

PostgreSQLでは、メモリ上に変更された行の情報を記憶しないため、同時にロックできる行数の上限はありません。 しかし、行をロックする際に、ディスクに書き込む作業が発生するかもしれません。 従って、例えば`SELECT FOR UPDATE`は選択された行に印を付けて変更を行い、ディスクにその結果を書き込むことになります。

テーブルと行ロックに加え、ページレベルの共有/排他ロックがあり、これらは共有バッファプールにあるテーブルページへの読み書きのアクセスを管理するために使用されます。 これらのロックは行が取得された後や更新された後に、即座に解除されます。 アプリケーション開発者は通常ページレベルロックを考慮する必要はありません。 ロックについて全てを説明したかったためページレベルロックを説明しました。

## 12.3.3. デッドロック

明示的なロックの使用は、デッドロックの原因となる可能性があります。 デッドロックとは、2つ(もしくはそれ以上)のトランザクションにおいて、それぞれが、他方のトランザクションが必要とするロックを所持してしまうことです。 例えば、トランザクション1がテーブルAに排他ロックを獲得していて、次にテーブルBに排他ロックを獲得しようとする際に、トランザクション2がすでにテーブルBに排他ロックを獲得済みであって、今からテーブルAに排他ロックを獲得しようと試みる場合、どちらのトランザクションも処理を進められません。 PostgreSQLでは、自動的にデッドロック状況を検知し、関係するトランザクションの一方をアボートすることにより、この状況を解決し、もう一方のトランザクションの処理を完了させます。 (どちらのトランザクションをアボートするかを正確に予期するのは難しく、これに依存すべきではありません。)

デッドロックは行レベルロックの結果として発生する可能性があります。 (従って、明示的なロック処理を使用していなくても発生する可能性があります。) 2つの同時実行トランザクションがあるテーブルを変更する状況を考えてみます。 1つ目のトランザクションは以下を実行します。

    UPDATE accounts SET balance = balance + 100.00 WHERE acctnum = 11111;

これは、指定した口座番号の行に対し行レベルロックを獲得します。 次に2番目のトランザクションが以下を実行します。

    UPDATE accounts SET balance = balance + 100.00 WHERE acctnum = 22222;
    UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 11111;

1つ目の`UPDATE`文は指定された行に対する行レベルロックの獲得に成功し、この行の更新に成功します。 しかし、2つ目の`UPDATE`文は、更新対象の行がロックされていることを検知し、ロックを獲得したトランザクションが完了するまで待機します。 トランザクション2は、ここで、続きを実行する前にトランザクション1が完了するのを待機しています。 さて、トランザクション1がここで以下を実行します。

    UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 22222;

トランザクション1は指定した行の行レベルロックを獲得しようとしますが、これは不可能です。 トランザクション2がそのロックを既に獲得しているからです。 そのため、トランザクション2が完了するのを待機することになります。 こうして、トランザクション1はトランザクション2でブロックされ、トランザクション2はトランザクション1でブロックされる、つまり、デッドロック状態です。 PostgreSQLはデッドロック状態を検知し、片方のトランザクションを中断させます。

デッドロックを防ぐ最も良い方法は、データベースを使用する全てのアプリケーションが、整合性のある順序で複数のオブジェクトに対するロックを獲得することです。 前に示したデッドロックの例で、もし両方のトランザクションで同じ順序で行を更新していたらデッドロックは起こりません。 また、トランザクション内のオブジェクトに対して獲得した最初のロックが、そのオブジェクトが必要とする最高位のモードにするを確実にすべきです。 このことが事前に検証できない場合、デッドロックによりアボートされたトランザクションを再試行すれば、デッドロックをデータベースを稼働させながらでも処理することができます。

デッドロック状況が検出されなければ、テーブルレベルロックもしくは行レベルロックを要求するトランザクションは、競合するロックが解放されるまで、無期限に待機します。 従って、アプリケーションで長時間(例えば、ユーザの入力待ち)トランザクションを開いたまま保持しておくのは、推奨されません。

前のページ | ホーム | 次のページ
トランザクションの隔離 | 上に戻る | アプリケーションレベルでのデータの一貫性チェック