ロックとは
ロックとは、あるトランザクションがレコードにアクセスしているときに,別のトランザクションからそのレコードにアクセスできないようにする仕組みのことです。
PostgreSQLの場合、トランザクションからの同時実行を確実にするために、テーブル・行に対して明示的なロックを獲得することができます。
また、ほとんどのPostgreSQLコマンドでは、参照されるテーブルがそのコマンドの実行中に別の方法で削除もしくは変更されていないことを確実にするために、SQLの実行の裏で、適切なモードのロックを自動的に獲得します。
テーブル単位のロック
テーブル単位のロックは、LOCK文を使用します。
LOCK [TABLE] table_name [IN lock_mode MODE] [NOWAIT]
ターミナルを2つ開いて試してみます。
transaction_a=# BEGIN;
transaction_a=# LOCK TABLE hoge_table IN EXCLUSIVE MODE;
transaction_b=# BEGIN;
transaction_b=# UPDATE hoge_table SET hoge_column = 'fuga';
-- トランザクションaでロック中のテーブルにUPDATEをかけているため、waitになる
transaction_a=# COMMIT;
transaction_b=# UPDATE num -- UPDATE分が実行される
※ NOWAIT:ロックが取得でないとき、待たずにエラーを返す
ロックモード
ロックモードは色々あるので、公式ドキュメントをご参照ください。
行単位のロック
行単位のロックは、SELECT文のオプションとしてSELECT FOR UPDATE
またはSELECT FOR SHARE
を使用します。
ターミナルを2つ開いて試してみます。
transaction_a=# BEGIN;
transaction_b=# BEGIN;
transaction_a=# UPDATE hoge_table SET hoge_column = 'fuga' where id = 1;
transaction_b=# SELECT * FROM hoge_table WHERE id = 1;
-- ロックしようとしている行の変更がトランザクションaでコミットされてないため、ここでwaitになる
transaction_a=# COMMIT;
transaction_b=# -- SELECT文の結果が表示される
デッドロック
デッドロックとは、二つのトランザクションが,互いに相手の所有するロックが解除されるのを待機して処理が進まなくなる状態のことです。
例えば次のような例を考えてみます。
transaction_a=# BEGIN;
transaction_b=# BEGIN;
transaction_a=# UPDATE hoge_table SET hoge_column = 'fuga' where id = 1;
transaction_b=# UPDATE hoge_table SET hoge_column = 'fuga' where id = 2;
transaction_b=# UPDATE hoge_table SET hoge_column = 'fuga' where id = 1;
-- トランザクションaでコミットされていないためwaitになる。トランザクションbはトランザクションaが完了するのを待つ
transaction_a=# UPDATE hoge_table SET hoge_column = 'fuga' where id = 2;
-- トランザクションbでコミットされていないためwaitになる。トランザクションaはトランザクションbが完了するのを待つ
-- トランザクションaもトランザクションbも、お互いのトランザクションが完了するのを待っているため、これ以上進まない。そのため、デッドロックが発生する
ERROR: deadlock detected
DETAIL: Process 93169 waits for ShareLock on transaction 112598; blocked by process 93173.
Process 93173 waits for ShareLock on transaction 112596; blocked by process 93169.
transaction_a=# SELECT * FROM hoge_table;
-- トランザクションがアボートされているのでエラーになる
ERROR: current transaction is aborted, commands ignored until end of transaction block
上記の場合、トランザクションaもトランザクションbも、お互いのトランザクションが完了するのを待っているため、これ以上処理を進めることができません。これがデッドロックであり、そうなると片方のトランザクションを強制終了することで、もう片方の処理を完了させます。
勧告的ロック
SQL文による自動ロックや明示的ロックの他に、アプリケーション固有の排他的制御を行いたい場合、勧告的ロックを使用します。勧告的ロックは、MVCC方式に合わせづらいロック戦略で有用に使用することができます。例えば、トランザクションをまたいで排他的制御を行い場合、セッションレベルでのロックを使用することができます。
参考
- 13.3. 明示的ロック
- 内部構造から学ぶPostgreSQL 設計・運用計画の鉄則 4.5