すべてのプロダクト
Search
ドキュメントセンター

PolarDB:明示的なロック

最終更新日:Jun 03, 2024

PolarDB PostgreSQLは、テーブル内のデータへの同時アクセスを制御するさまざまなロックモードを提供します。 これらのモードは、MVCCが所望の動作を与えない状況で、アプリケーション制御のロッキングに使用することができる。 また、ほとんどのコマンドは、適切なモードのロックを自動的に取得して、コマンドの実行中に参照テーブルが互換性のない方法で削除または変更されないようにします。 (たとえば、TRUNCATEは同じテーブルで他の操作と同時に安全に実行できないため、テーブルでACCESS EXCLUSIVEロックを取得してそれを強制します) 。

データベースサーバーで現在未処理のロックのリストを調べるには、pg_locksシステムビューを使用します。

テーブルレベルロック

以下のリストは、使用可能なロックモードと、それらがPostgreSQLによって自動的に使用されるコンテキストを示しています。 これらのロックは、LOCKコマンドで明示的に取得することもできます。 名前に "row" という単語が含まれている場合でも、これらのロックモードはすべてテーブルレベルのロックであることに注意してください。ロックモードの名前は履歴的です。 名前は、各ロックモードの一般的な使用法をある程度反映していますが、セマンティクスはすべて同じです。 1つのロックモードと別のロックモードの唯一の本当の違いは、それぞれが競合するロックモードのセットです。 2つのトランザクションは、同じテーブル上の競合モードのロックを同時に保持することはできません。 (ただし、トランザクションはそれ自体と競合することはありません。 たとえば、ACCESS EXCLUSIVEロックを取得し、後で同じテーブルのACCESS SHAREロックを取得します。競合しないロックモードは、多くのトランザクションによって同時に保持することができる。 特に、いくつかのロックモードは自己競合しており (たとえば、ACCESS EXCLUSIVEロックは一度に複数のトランザクションで保持できません) 、他のロックモードは自己競合していません (たとえば、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)

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

    コマンドUPDATEDELETE、およびINSERTは、(他の参照テーブルのACCESS SHAREロックに加えて) ターゲットテーブルのこのロックモードを取得します。 一般に、このロックモードは、テーブル内のデータを変更する任意のコマンドによって取得されます。

  • SHARE UPDATE EXCLUSIVE (ShareUpdateExclusiveLock)

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

    VACUUM (FULLなし) 、ANALYZECREATE INDEX CONCURNTLYCREATE STATISTICSCOMMENT ONREINDEX CONCURNTLY、および特定のALTER INDEXおよびALTER TABLEのバリエーションで取得されます。

  • シェア (ShareLock)

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

    CREATE INDEXで取得 (CONCURRENTLYなし) 。

  • SHARE ROW EXCLUSIVE (ShareRowExclusiveLock)

    ROW EXCLUSIVESHARE UPDATE EXCLUSIVESHARESHARE ROW EXCLUSIVEEXCLUSIVE、およびACCESS EXCLUSIVEロックモードと競合します。 このモードは、同時データ変更からテーブルを保護し、一度に1つのセッションのみがテーブルを保持できるように自己排他的です。

    CREATE TRIGGERといくつかの形式のALTER TABLEによって取得されます。

  • 排他的 (ExclusiveLock)

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

    REFRESH MATERIALIZED VIEW CONCURRENTLYによって取得されました。

  • 排他的アクセス (AccessExclusiveLock)

    すべてのモード (ACCESS SHAREROW SHAREROW EXCLUSIVESHARE UPDATE EXCLUSIVESHARESHARE ROW EXCLUSIVEEXCLUSIVEACCESS EXCLUSIVE) のロックと競合します。 このモードは、保持者が何らかの方法でテーブルにアクセスする唯一のトランザクションであることを保証する。

    DROP TABLETRUNCATEREINDEXCLUSTERVACUUM FULL、およびREFRESH MATERIALIZED VIEW (CONCURRENTLYなし) コマンドによって取得されます。 多くの形式のALTER INDEXALTER TABLEもこのレベルでロックを取得します。 これは、モードを明示的に指定しないlock TABLEステートメントのデフォルトのロックモードでもあります。

    説明

    ACCESS EXCLUSIVEロックのみがSELECT (FOR UPDATE/SHAREなし) ステートメントをブロックします。

一旦取得されると、ロックは通常、トランザクションの終了まで保持される。 ただし、セーブポイントを確立した後にロックが取得された場合、セーブポイントがロールバックされると、ロックはすぐに解放されます。 これは、ROLLBACKがセーブポイント以降のコマンドのすべての効果をキャンセルするという原則と一致しています。 同じことがaPL/pgSQLexceptionブロック内で取得されたロックにも当てはまります。ブロックからのエラーエスケープは、そのブロック内で取得されたロックを解放します。

要求されたロックモード

既存のロックモード

アクセスシェア

ROWシェア

ROW EXCL。

SHARE UPDATE EXCL。

シェア

SHARE ROW EXCL。

EXCL。

アクセスEXCL。

アクセスシェア

X

ROWシェア

X

X

ROW EXCL。

X

X

X

X

SHARE UPDATE EXCL。

X

X

X

X

X

シェア

X

X

X

X

X

SHARE ROW EXCL。

X

X

X

X

X

X

EXCL。

X

X

X

X

X

X

X

アクセスEXCL。

X

X

X

X

X

X

X

X

行レベルロック

テーブルレベルのロックに加えて、行レベルのロックがあります。これらのロックは、PolarDB PostgreSQLによって自動的に使用されるコンテキストとともに以下のようにリストされています。 トランザクションは、異なるサブトランザクションでも、同じ行で競合するロックを保持できますが、それ以外は、2つのトランザクションが同じ行で競合するロックを保持することはできません。 行レベルのロックは、データのクエリには影響しません。同じ行へのライターとロッカーのみをブロックします。 行レベルのロックは、テーブルレベルのロックと同様に、トランザクションの終了時またはセーブポイントロールバック中にリリースされます。

行レベルロックモード

  • 更新のため

    FOR UPDATEでは、SELECTステートメントによって取得された行が更新のようにロックされます。 これにより、現在のトランザクションが終了するまで、他のトランザクションによってロック、変更、または削除されなくなります。 つまり、これらの行のUPDATEDELETESELECT FOR UPDATESELECT FOR NO KEY UPDATESELECT FOR SHARESELECT FOR KEY SHAREを試みる他のトランザクションは、現在のトランザクションが終了するまでブロックされます。更新された行をロックして返します (行が削除された場合は行なし) 。 ただし、REPEATABLE READまたはSERIALIZABLEトランザクション内では、トランザクションが開始されてからロックされる行が変更された場合、エラーがスローされます。

    FOR UPDATEロックモードは、行のDELETEと、特定の列の値を変更するUPDATEによっても取得されます。 現在、UPDATEケースで考慮される列のセットは、外部キーで使用できる一意のインデックスを持つ列のセットです (したがって、部分インデックスと表現インデックスは考慮されません) が、これは将来変更される可能性があります。

  • キーアップデートなし

    FOR UPDATEと同様に動作しますが、取得したロックが弱い点が異なります。このロックは、同じ行でロックを取得しようとするSELECT FOR KEY SHAREコマンドをブロックしません。 このロックモードは、FOR UPDATEロックを取得しないUPDATEによっても取得されます。

  • シェア

    FOR NO KEY UPDATEと同様に動作しますが、取得した各行で排他ロックではなく共有ロックを取得する点が異なります。 共有ロックは、他のトランザクションがこれらの行でUPDATEDELETESELECT FOR UPDATEまたはSELECT FOR NO KEY UPDATEを実行するのをブロックしますが、SELECT FOR SHAREまたはSELECT FOR KEY SHAREを実行するのを妨げません。

  • キーシェア

    ロックが弱いことを除いて、FOR SHAREと同様に動作します。SELECT FOR UPDATEはブロックされますが、SELECT FOR NO KEY UPDATEはブロックされません。 キー共有ロックは、他のトランザクションがDELETEまたはキー値を変更するUPDATEを実行するのをブロックしますが、他のUPDATEはブロックしません。また、SELECT FOR NO key UPDATESELECT FOR SHARE、またはSELECT FOR SHAREもブロックしません。

PolarDB PostgreSQLは、メモリ内の変更された行に関する情報を記憶していないため、一度にロックされる行の数に制限はありません。 ただし、行をロックするとディスク書き込みが発生する可能性があります。たとえば、SELECT FOR UPDATEは、選択した行を変更してロックされた行をマークするため、ディスク書き込みが発生します。

要求されたロックモード

現在のロックモード

キーシェア

シェア

キーアップデートなし

更新のため

キーシェア用

X

シェア用

X

X

キーアップデートなし

X

X

X

更新のため

X

X

X

X

ページレベルロック

テーブルおよび行ロックに加えて、ページレベルの共有 /排他ロックを使用して、共有バッファプール内のテーブルページへの読み取り /書き込みアクセスを制御します。 これらのロックは、行がフェッチまたは更新された直後に解放されます。 アプリケーション開発者は通常、ページレベルのロックに関心を持つ必要はありませんが、完全性のためにここで言及します。

デッドロック

明示的なロッキングの使用は、デッドロックの可能性を高めることができ、2つ (またはそれ以上) のトランザクションがそれぞれ、他方が望むロックを保持する。 例えば、トランザクション1がテーブルAの排他的ロックを取得し、次にテーブルBの排他的ロックを取得しようとするが、トランザクション2が既に排他的ロックされたテーブルBを有し、テーブルAの排他的ロックを望んでいる場合、どちらも先に進むことができない。 PolarDB PostgreSQLは自動的にデッドロック状況を検出し、関連するトランザクションの1つを中止して解決し、他のトランザクションを完了させます。 (正確にどのトランザクションが中止されるかは予測が難しく、信頼されるべきではありません。)

デッドロックは、行レベルのロックの結果としても発生する可能性があることに注意してください (したがって、明示的なロックが使用されない場合でも発生する可能性があります) 。 2つの同時トランザクションがテーブルを変更する場合を考える。 最初のトランザクションが実行されます。

UPDATEアカウントSET balance = balance + 100.00 WHERE acctnum = 11111;

これにより、指定されたアカウント番号の行に対する行レベルのロックが取得されます。 次に、2番目のトランザクションが実行されます。

UPDATEアカウントSET balance = balance + 100.00 WHERE acctnum = 22222;
UPDATEアカウントSET balance = balance - 100.00 WHERE acctnum = 11111; 

最初のUPDATEステートメントは、指定された行の行レベルのロックを正常に取得するため、その行の更新に成功します。 ただし、2番目のUPDATEステートメントでは、更新しようとしている行がすでにロックされていることがわかり、ロックを取得したトランザクションが完了するのを待ちます。 トランザクション2は、トランザクション1が実行を継続する前に完了するのを待っている。 これで、トランザクション1が実行されます。

UPDATEアカウントSET balance = balance - 100.00 WHERE acctnum = 22222;

トランザクション1は、指定された行で行レベルのロックを取得しようとしますが、取得できません。トランザクション2はすでにそのようなロックを保持しています。 したがって、トランザクション2が完了するのを待ちます。 したがって、トランザクション1はトランザクション2でブロックされ、トランザクション2はトランザクション1でブロックされます。デッドロック状態PostgreSQLはこの状況を検出し、トランザクションの1つをアボートします。

デッドロックに対する最善の防御は、一般に、データベースを使用するすべてのアプリケーションが一貫した順序で複数のオブジェクトのロックを取得することを確認することによって、デッドロックを回避することです。 上記の例では、両方のトランザクションが同じ順序で行を更新した場合、デッドロックは発生しません。 また、トランザクション内のオブジェクトで取得された最初のロックが、そのオブジェクトに必要とされる最も制限的なモードであることを確認する必要があります。 これを事前に検証することが実現可能でない場合、デッドロックのためにアボートするトランザクションを再試行することによってデッドロックをオンザフライで処理することができる。

デッドロック状況が検出されない限り、テーブルレベルまたは行レベルのロックのいずれかを求めるトランザクションは、競合するロックが解放されるまで無期限に待機する。 これは、アプリケーションがトランザクションを長期間開いたままにしておくことは悪い考えであることを意味します (たとえば、ユーザーの入力を待っている間) 。

アドバイザリーロック

PolarDB PostgreSQLは、アプリケーション定義の意味を持つロックを作成する手段を提供します。 これらは、システムがそれらの使用を強制しないため、アドバイザリロックと呼ばれます-それらを正しく使用するのはアプリケーション次第です。 アドバイザリロックは、MVCCモデルに適合しにくいロック戦略に役立ちます。 例えば、アドバイザリロックの一般的な使用は、いわゆる「フラットファイル」データ管理システムに典型的な悲観的ロック戦略をエミュレートすることである。 テーブルに格納されたフラグは同じ目的で使用できますが、アドバイザリロックはより高速で、テーブルの肥大を回避し、セッションの終了時にサーバーによって自動的にクリーンアップされます。

PolarDB PostgreSQLでアドバイザリロックを取得するには、セッションレベルとトランザクションレベルの2つの方法があります。 セッションレベルで取得されると、明示的に解放されるか、セッションが終了するまでアドバイザリロックが保持されます。 標準のロック要求とは異なり、セッションレベルのアドバイザリロック要求は、トランザクションのセマンティクスを尊重しません。後でロールバックされるトランザクション中に取得されたロックは、ロールバック後も保持されます。 ロックは、その所有プロセスによって複数回取得できます。完了したロック要求ごとに、ロックが実際に解放される前に、対応するロック解除要求が存在する必要があります。 一方、トランザクションレベルのロック要求は、通常のロック要求のように動作します。トランザクションの最後に自動的にリリースされ、明示的なロック解除操作はありません。 この動作は、アドバイザリロックを短期間使用する場合、セッションレベルの動作よりも便利です。 同じアドバイザリロック識別子に対するセッションレベルおよびトランザクションレベルのロック要求は、予想される方法で互いにブロックする。 セッションがすでに特定のアドバイザリロックを保持している場合、他のセッションがロックを待機している場合でも、セッションによる追加の要求は常に成功します。このステートメントは、既存のロック保持と新しい要求がセッションレベルまたはトランザクションレベルのいずれであるかに関係なく真です。

PolarDB PostgreSQLのすべてのロックと同様に、現在どのセッションでも保持されているアドバイザリロックの完全なリストは、pg_locksシステムビューにあります。

アドバイザリロックとレギュラーロックはどちらも、構成変数max_locks_per_transactionmax_connectionsによってサイズが定義される共有メモリプールに格納されます。 このメモリを使い果たさないように注意する必要があります。そうしないと、サーバーはロックをまったく許可できなくなります。 これは、サーバによって与えられるアドバイザリロックの数に上限を課し、典型的には、サーバがどのように構成されるかに応じて、数万から数十万である。

アドバイザリロッキングメソッドを使用する場合、特に明示的な順序付けとLIMIT句を含むクエリでは、SQL式が評価される順序のために、取得されるロックを制御するように注意する必要があります。 設定例:

SELECT pg_advisory_lock(id) FROM foo WHERE id = 12345; -- ok
    SELECT pg_advisory_lock(id) FROM foo WHERE id > 12345 LIMIT 100; -- danger!
    SELECT pg_advisory_lock(q.id) FROM
    (
      SELECT id FROM foo WHERE id > 12345リミット100
    ) q; -- ok 

上記のクエリでは、ロック機能が実行される前にLIMITが適用されることが保証されないため、2番目の形式は危険です。 これにより、アプリケーションが予期していなかったいくつかのロックが取得される可能性があり、したがって (セッションが終了するまで) 解放に失敗する可能性があります。 アプリケーションの観点からは、このようなロックは、pg_locksではまだ表示できますが、ぶら下がっています。