PostgreSQLは、テーブル内のデータに対する同時アクセスを制御するために様々な種類のロックモードを備えています。
これらのモードは、MVCCでは必要な動作を得られない場合、アプリケーション制御のロックに使用することができます。
また、ほとんどのPostgreSQLコマンドでは、参照されるテーブルがそのコマンドの実行中に別の方法で削除もしくは変更されていないことを確実にするために、適切なモードのロックを自動的に獲得します。
(例えば、TRUNCATE
コマンドは、同じテーブルに対する他の操作と同時に安全に実行することはできないので、それを確実に実行するため、そのテーブルのACCESS EXCLUSIVE
ロックを獲得します。)
現在のデータベースサーバに残っているロックの一覧を確認するには、pg_locks
システムビューを使用してください。
ロック管理サブシステムの状況監視についての詳細は第28章を参照してください。
以下のリストに、使用可能なロックモードとそれらがPostgreSQLで自動的に使用される文脈を示します。
また、LOCKコマンドを使用して、こうしたロックを明示的に獲得することもできます。
これらのロックモードは、たとえその名前に「row(行)」という言葉が付いていても、全てテーブルレベルのロックであることに注意してください。
ロックモードの名前は歴史的なものです。
これらの名前は、各ロックモードの代表的な使用方法をある程度表しています。
しかし、意味的には全て同じです。
ロックモード間における唯一の実質的な差異は、どのモードがどのモードと競合するかというロックモードの組み合わせです(表 13.2を参照してください)。
2つのトランザクションで、競合するモードのロックを同時に同一テーブル上に保持することはできません
(しかし、トランザクションは自分自身とは決して競合しません。
例えば、ACCESS EXCLUSIVE
ロックを獲得し、その後同じテーブルにACCESS SHARE
ロックを獲得できる可能性があります)。
競合しないロックモードは、多くのトランザクションで同時に保持することが可能です。
特に、ロックモードには、自己競合するもの(例えば、ACCESS EXCLUSIVE
は同時に複数のトランザクションで保持することは不可能)と、自己競合しないもの(例えば、ACCESS SHARE
は複数のトランザクションで保持可能)があることに注意してください。
テーブルレベルロックモード
ACCESS SHARE
(AccessShareLock
)
ACCESS EXCLUSIVE
ロックモードとのみ競合します。
SELECT
コマンドにより、参照されるテーブルに対してこのモードのロックが獲得されます。
通常、テーブルの読み取りのみで変更を行わない問い合わせであれば全て、このロックモードを獲得します。
ROW SHARE
(RowShareLock
)
EXCLUSIVE
およびACCESS EXCLUSIVE
ロックモードと競合します。
SELECT FOR UPDATE
およびSELECT FOR SHARE
コマンドは、(参照はされているが、FOR UPDATE/FOR SHARE
として選択はされていない他のテーブルに対するACCESS SHARE
ロックに加えて)対象となるテーブル上にこのモードのロックを獲得します。
ROW EXCLUSIVE
(RowExclusiveLock
)
SHARE
、SHARE ROW EXCLUSIVE
、EXCLUSIVE
、およびACCESS EXCLUSIVE
ロックモードと競合します。
UPDATE
、DELETE
、およびINSERT
コマンドは、(参照される他の全てのテーブルに対するACCESS SHARE
ロックに加えて)対象となるテーブル上にこのモードのロックを獲得します。
通常、このロックモードは、テーブルのデータを変更する問い合わせにより獲得されます。
SHARE UPDATE EXCLUSIVE
(ShareUpdateExclusiveLock
)
SHARE UPDATE EXCLUSIVE
、SHARE
、SHARE ROW EXCLUSIVE
、EXCLUSIVE
、およびACCESS EXCLUSIVE
ロックモードと競合します。
このモードにより、同時実行されるスキーマの変更およびVACUUM
コマンドの実行から、テーブルを保護します。
(FULL
なしの)VACUUM
、ANALYZE
、CREATE INDEX CONCURRENTLY
、CREATE STATISTICS
、COMMENT ON
、REINDEX CONCURRENTLY
、ある種のALTER INDEX
、ALTER TABLE
の変種で取得されます(完全な詳細はこれらのコマンドの文書を見てください)。
SHARE
(ShareLock
)
ROW EXCLUSIVE
、SHARE UPDATE EXCLUSIVE
、SHARE ROW EXCLUSIVE
、EXCLUSIVE
、およびACCESS EXCLUSIVE
ロックモードと競合します。
このモードは、同時実行されるデータ変更からテーブルを保護します。
(CONCURRENTLY
なしの)CREATE INDEX
によって獲得されます。
SHARE ROW EXCLUSIVE
(ShareRowExclusiveLock
)
ROW EXCLUSIVE
、SHARE UPDATE EXCLUSIVE
、
SHARE
、SHARE ROW EXCLUSIVE
、EXCLUSIVE
、およびACCESS EXCLUSIVE
ロックモードと競合します。
このモードは、1つのセッションだけが一度にそれを保持することができるよう、自己排他的に同時のデータ変更からテーブルを保護します。
CREATE TRIGGER
、および、ALTER TABLE
のいくつか形式により獲得されます。
EXCLUSIVE
(ExclusiveLock
)
ROW SHARE
、ROW EXCLUSIVE
、
SHARE UPDATE EXCLUSIVE
、SHARE
、
SHARE ROW EXCLUSIVE
、EXCLUSIVE
、およびACCESS EXCLUSIVE
ロックモードと競合します。
このモードは、同時実行されるACCESS SHARE
のみを許可します。
つまり、このロックモードを保持するトランザクションと並行して実行できる処理は、テーブルの読み取りだけです。
REFRESH MATERIALIZED VIEW CONCURRENTLY
により獲得されます。
ACCESS EXCLUSIVE
(AccessExclusiveLock
)
全てのモードのロック(ACCESS
SHARE
、ROW SHARE
、ROW
EXCLUSIVE
、SHARE UPDATE
EXCLUSIVE
、SHARE
、SHARE
ROW EXCLUSIVE
、EXCLUSIVE
、および
ACCESS EXCLUSIVE
)と競合します。
このモードにより、その保持者以外にテーブルにアクセスするトランザクションがないことが保証されます。
DROP TABLE
、TRUNCATE
、REINDEX
、CLUSTER
、VACUUM FULL
、(CONCURRENTLY
なしの)REFRESH MATERIALIZED VIEW
コマンドによって獲得されます。
ALTER INDEX
とALTER TABLE
の多くの形式もこのレベルでロックを獲得します。
これはまた、明示的にモードを指定しないLOCK TABLE
文のデフォルトのロックモードです。
ACCESS EXCLUSIVE
ロックのみが、SELECT
(FOR UPDATE/SHARE
なし)文をブロックします。
通常ロックは獲得した後、トランザクションの終わりまで保持されます。
しかし、ロックがセーブポイントの確立後に獲得された場合、セーブポイントがロールバックされると、ロックは即座に解放されます。
これは、ROLLBACK
がセーブポイント以降に行われたすべてのコマンドの効果を取消すという原則と整合性が取れています。
PL/pgSQL例外ブロック内で獲得されたロックに対しても同様です。
そのブロックからエラーで抜けた後、獲得されたロックは解放されます。
表13.2 ロックモードの競合
要求するロックモード | 既存のロックモード | |||||||
---|---|---|---|---|---|---|---|---|
ACCESS SHARE | ROW SHARE | ROW EXCL. | SHARE UPDATE EXCL. | SHARE | SHARE ROW EXCL. | EXCL. | ACCESS EXCL. | |
ACCESS SHARE | X | |||||||
ROW SHARE | X | X | ||||||
ROW EXCL. | X | X | X | X | ||||
SHARE UPDATE EXCL. | X | X | X | X | X | |||
SHARE | X | X | X | X | X | |||
SHARE ROW EXCL. | X | X | X | X | X | X | ||
EXCL. | X | X | X | X | X | X | X | |
ACCESS EXCL. | X | X | X | X | X | X | X | X |
テーブルレベルロックに加えて、行レベルロックがあります。PostgreSQLが自動的に使う文脈付きで以下に行レベルロックの一覧があります。 行レベルロックの競合の完全な表については表 13.3を参照してください。 トランザクションは異なる副トランザクション内であっても、同じ行に対して競合するロックを保持できることに注意してください。 しかし、それ以外では、二つのトランザクションは同じ行に対して競合するロックを決して保持できません。 行レベルロックは、データの問い合わせには影響を与えません。 行レベルロックは、同じ行に対する書き込みとロックだけをブロックします。 テーブルレベルロックと同じように、行レベルロックはトランザクションの終わり、または、セーブポイントへのロールバックで解放されます。
行レベルロックモード
FOR UPDATE
FOR UPDATE
によりSELECT
文により取り出された行が更新用であるかのようにロックされます。
これにより、それらは現在のトランザクションが終わるまで、他のトランザクションがロック、変更、削除できなくなります。
すなわち、これらの行に対してUPDATE
、DELETE
、SELECT FOR UPDATE
、SELECT FOR NO KEY UPDATE
、SELECT FOR SHARE
、SELECT FOR KEY SHARE
をしようとする他のトランザクションは現在のトランザクションが終わるまでブロックされます。逆に言えば、SELECT FOR UPDATE
は同じ行に対して上記のコマンドを実行している同時実行トランザクションを待ち、それから更新された行をロックして返します(行が削除されていれば、行は返しません)。
しかし、REPEATABLE READ
もしくはSERIALIZABLE
トランザクション内では、ロックする行がトランザクションの開始した後に変更された場合にはエラーが返ります。
これ以上の議論は13.4を参照してください。
FOR UPDATE
ロックモードは行に対するDELETE
でも、ある列の値を変更するUPDATE
でも獲得されます。
現時点では、UPDATE
の場合に考慮される列の集合は、外部キーとして使うことのできる一意のインデックス(つまり部分インデックスや式インデックスは考慮されません)があるものですが、これは将来変わるかもしれません。
FOR NO KEY UPDATE
獲得するロックが弱い以外はFOR UPDATE
と同じように振る舞います。このロックは同じ行のロックを獲得しようとするSELECT FOR KEY SHARE
コマンドをブロックしません。
このロックモードはFOR UPDATE
ロックを獲得しないUPDATE
によっても獲得されます。
FOR SHARE
取り出された各行に対して排他ロックではなく共有ロックを獲得する以外は、FOR NO KEY UPDATE
と同じように振る舞います。
共有ロックは、他のトランザクションがこれらの行に対してUPDATE
、DELETE
、SELECT FOR UPDATE
、SELECT FOR NO KEY UPDATE
を実行するのをブロックしますが、SELECT FOR SHARE
やSELECT FOR KEY SHARE
を実行するのを阻害しません。
FOR KEY SHARE
獲得するロックが弱い以外はFOR SHARE
と同じように振る舞います。SELECT FOR UPDATE
はブロックされますが、SELECT FOR NO KEY UPDATE
はブロックされません。
キー共有ロックは、他のトランザクションがDELETE
やキー値を変更するUPDATE
を実行するのをブロックしますが、それ以外のUPDATE
や、SELECT FOR NO KEY UPDATE
、SELECT FOR SHARE
、SELECT FOR KEY SHARE
を阻害しません。
PostgreSQLでは、メモリ上に変更された行の情報を記憶しないため、同時にロックできる行数の上限はありません。
しかし、行をロックする際に、ディスクに書き込む作業が発生するかもしれません。
例えばSELECT FOR UPDATE
は、選択された行をロックしたものと印を付けるために変更を行いますので、ディスクにその結果を書き込むことになります。
表13.3 行レベルロックの競合
要求するロックモード | 現在のロックモード | |||
---|---|---|---|---|
FOR KEY SHARE | FOR SHARE | FOR NO KEY UPDATE | FOR UPDATE | |
FOR KEY SHARE | X | |||
FOR SHARE | X | X | ||
FOR NO KEY UPDATE | X | X | X | |
FOR UPDATE | X | X | X | X |
テーブルと行ロックに加え、ページレベルの共有/排他ロックがあり、これらは共有バッファプールにあるテーブルページへの読み書きのアクセスを管理するために使用されます。 これらのロックは、行が取得された後や更新された後に即座に解除されます。 アプリケーション開発者は通常ページレベルロックを考慮する必要はありませんが、ロックについて全てを説明したかったためここで取り上げました。
明示的なロックの使用は、デッドロックの原因となる可能性があります。 デッドロックとは、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はデッドロック状態を検知し、片方のトランザクションを中断させます。
デッドロックを防ぐ最も良い方法は、データベースを使用する全てのアプリケーションが、整合性のある順序で複数のオブジェクトに対するロックを獲得することです。 前に示したデッドロックの例で、もし両方のトランザクションで同じ順序で行を更新していたらデッドロックは起こりません。 また、トランザクション内のオブジェクトに対して獲得した最初のロックが、そのオブジェクトが必要とする最も制限的なモードであることを確実に保証すべきです。 このことが事前に検証できない場合、デッドロックによりアボートするトランザクションを再試行すれば、デッドロックをデータベースを稼働させながらでも処理することができます。
デッドロック状況が検出されなければ、テーブルレベルロックもしくは行レベルロックを要求するトランザクションは、競合するロックが解放されるまで、無期限に待機します。 したがって、アプリケーションで長時間(例えば、ユーザの入力待ち)トランザクションを開いたまま保持しておくのは、推奨されません。
PostgreSQLは、アプリケーション独自の意味を持つロックを生成する手法を提供します。 これは、その使用に関してシステムによる制限がないこと、つまり、正しい使用に関してはアプリケーションが責任を持つことから勧告的ロックと呼ばれます。 勧告的ロックは、MVCC方式に合わせづらいロック戦略で有用に使用することができます。 例えば、勧告的ロックのよくある利用として、いわゆる「フラットファイル」データ管理システムで典型的な、悲観的なロック戦略を模擬することです。 この用途のためにテーブル内にフラグを格納することもできますが、勧告的ロックの方が高速で、テーブルの膨張を防ぐことができます。 また、セッション終了時にサーバによる自動整理を行うこともできるようになります。
PostgreSQLには、セッションレベルとトランザクションレベルという2つの勧告的ロックの獲得方法があります。 セッションレベルで獲得すると、勧告的ロックは明示的に解放されるか、セッションが終了するまで保持されます。 標準のロック要求と異なり、セッションレベル勧告的ロックはトランザクションという意味には従いません。 ロックがトランザクション期間中に獲得され、そのトランザクションを後でロールバックしたとしても、ロールバック後も保持されます。 そして、呼び出し元のトランザクションが後で失敗したとしてもロック解除は有効です。 所有するプロセスの中で、同一のセッションレベルのロックを複数回獲得することもできます。 この場合、個々のロック要求に対して、ロックを実際に解放する前に対応するロック解除要求がなければなりません。 一方トランザクションレベルのロックはより通常のロックに似たように動作します。 それらは、処理の終わりに自動的に解放されますので、明示的なロック解放操作はありません。 短期間の勧告的ロックを利用する場合は、セッションレベルの動作よりもこの動作の方が便利なことが多くあります。 同じ勧告的ロック識別子に対するセッションレベルのロックとトランザクションレベルのロック要求は、想像通り互いをブロックします。 セッションがすでに指定された勧告的ロックを保持している場合、他のセッションがそのロックを待機していたとしても、追加の要求は常に成功します。 これは保持されているロックと新しい要求がセッションレベルかトランザクションレベルかどうかに関わらず、この文は当てはまります。
PostgreSQLにおけるすべてのロックと同様に、現時点ですべてのセッションで保持されている勧告的ロックの全一覧はpg_locks
システムビューにあります。
勧告的ロックと通常のロックは共有メモリプールに割り当てられ、その容量はmax_locks_per_transactionとmax_connections設定変数により決定されます。 このメモリを浪費しないように注意が必要です。 さもないと、サーバはロック獲得をまったく許可することができなくなります。 これは、サーバで許可できる勧告的ロック数に上限があることを意味します。 サーバの設定によりますが、通常、1万から10万程度になります。
特に明示的な順序付けとLIMIT
句を持つ問い合わせでは、この勧告ロックモードを使用する幾つかの場合において、SQL式が評価される順序を考慮し獲得されたロックを制御することに気を配らなければなりません。
以下に例を示します。
SELECT pg_advisory_lock(id) FROM foo WHERE id = 12345; -- 問題なし SELECT pg_advisory_lock(id) FROM foo WHERE id > 12345 LIMIT 100; -- 危険! SELECT pg_advisory_lock(q.id) FROM ( SELECT id FROM foo WHERE id > 12345 LIMIT 100 ) q; -- 問題なし
上の例では、ロック獲得関数が実行される前にLIMIT
が適用されることを保障できないため、2番目の形式は危険です。
これにより、アプリケーションが想定していないなんらかのロックが生成される可能性があります。
そのため、(セッションが終了するまで)解放に失敗することになります。
アプリケーションから見ると、こうしたロックはただの飾りですが、pg_locks
からは参照され続けます。
勧告的ロックを扱うための関数については、9.27.10で説明します。