明示的なロックと自動的なロック

データベースがロックをかけるのは、明示的に指定した時だけではないというお話。

http://www.postgresql.jp/document/pg813doc/html/explicit-locking.html

PostgreSQL の明示的なロックについては上記ドキュメントの通りです。ドキュメントに記載された通り、複数のトランザクションから、競合するロックレベルで同じリソースへロックをかけようとすると待ちが発生します。
例えば、以下の順番で二つのトランザクションが実行された場合、先行するトランザクションAによるテーブル hoge の処理が完了するまで(トランザクションAがコミットされるタイミングまで)、トランザクションBは hoge へのロックを行う事ができず、処理は中断します。

トランザクションA

BEGIN;

トランザクションB

BEGIN;

トランザクションA

LOCK TABLE hoge IN SHARE ROW EXCLUSIVE MODE;

トランザクションB

-- ロックを獲得できず、待たされる
LOCK TABLE hoge IN SHARE ROW EXCLUSIVE MODE;

トランザクションA

-- このタイミングでロックが解除される
COMMIT;

また、以下のようにお互いがロックを保持したテーブルへアクセスしようとすると、典型的なデッドロックが起こります。

トランザクションA

BEGIN;

トランザクションB

BEGIN;

トランザクションA

LOCK TABLE hoge IN SHARE ROW EXCLUSIVE MODE;

トランザクションB

LOCK TABLE fuga IN SHARE ROW EXCLUSIVE MODE;

トランザクションB

-- ロックを獲得できず、待たされる
LOCK TABLE hoge IN SHARE ROW EXCLUSIVE MODE;

トランザクションA

-- ロック権が得られず永久に待たされるため deadlock_timeout で設定した秒数だけ待ってからデッドロックが検知される
LOCK TABLE fuga IN SHARE ROW EXCLUSIVE MODE;

ここまでは注意して構築を行う事が多いと思うのですが、問題は公式ドキュメントでも書かれている自動的なロックがかかるケースです。例えば、以下のような一見問題のない操作でもデッドロックは発生します。

トランザクションA

BEGIN;

トランザクションB

BEGIN;

トランザクションA

UPDATE hoge SET col = 'val';

トランザクションB

LOCK TABLE fuga IN SHARE ROW EXCLUSIVE MODE;

トランザクションB

-- ロックを取得する事ができない
LOCK TABLE hoge IN SHARE ROW EXCLUSIVE MODE;

トランザクションA

-- ここでデッドロックが発生
UPDATE fuga SET col = 'val';

公式ドキュメントの ROW EXCLUSIVE MODE でのロックについてきちんと読むと、以下のように記述されています。

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

つまり、明示的にロックを指定せずとも内部的には ROW EXCLUSIVE MODE でのロックがかけられているという事です。一方の SHARE ROW EXCLUSIVE MODE には

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

と記述されているので、競合が発生するというわけでした。PostgreSQLアクセスログを読む際には、自動ロックについても考慮しないと競合している箇所が発見できないかもしれないので気をつけてください。