12.3. 明示的ロック

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

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

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

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

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

ACCESS SHARE

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

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

ROW SHARE

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

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

ROW EXCLUSIVE

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

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

SHARE UPDATE EXCLUSIVE

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

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

SHARE

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

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

SHARE ROW EXCLUSIVE

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

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

EXCLUSIVE

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

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

ACCESS EXCLUSIVE

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

ALTER TABLEDROP TABLEREINDEXCLUSTERおよび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はデッドロック状態を検知し、片方のトランザクションを中断させます。

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

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