PostgreSQL 11 min read

PostgreSQLのロック待ちを調べる ― pg_blocking_pidsで止めている側を探す、lock_timeout

※この記事にはアフィリエイト広告(プロモーション)が含まれます。

この記事の出力は PostgreSQL 18.6(Ubuntu 26.04)で採りました。

まず見る(結論)

SELECT w.pid AS waiting_pid, w.query AS waiting_query,
       b.pid AS blocking_pid, b.state AS blocking_state, b.query AS blocking_query,
       now() - b.xact_start AS blocking_xact_age
  FROM pg_stat_activity w
  JOIN LATERAL unnest(pg_blocking_pids(w.pid)) AS bp(pid) ON true
  JOIN pg_stat_activity b ON b.pid = bp.pid;
  • 止めている側の state が idle in transaction なら、pg_cancel_backend では外れない。pg_terminate_backend で切る
  • blocking_query は止めている側が最後に実行した文で、ロックを取った文とは限らない

待っている接続を見つける

セッション A が id=1 を UPDATE したままコミットせず、セッション B が同じ行を UPDATE したときの pg_stat_activity です。

SELECT pid, state, wait_event_type, wait_event, pg_blocking_pids(pid) AS blocked_by, left(query, 60) AS query
  FROM pg_stat_activity WHERE datname = 'fsv' AND pid <> pg_backend_pid();

 pid  | state  | wait_event_type |  wait_event   | blocked_by |                    query
------+--------+-----------------+---------------+------------+---------------------------------------------
 1796 | active | Timeout         | PgSleep       | {}         | ... BEGIN; UPDATE acct SET bal=bal-10 WHERE ...
 1801 | active | Lock            | transactionid | {1796}     | ... UPDATE acct SET bal=bal+10 WHERE id=1;

wait_event_type が Lock の行が待っている接続で、pg_blocking_pids が止めている側の pid を配列で返します。行ロックの待ちは wait_event が transactionid、表ロックの待ちは relation になりました。

pg_locks を見ると、待っている側は locktype が transactionid・mode が ShareLock・granted が f の行を持っていました。待つ対象は行ではなく、相手のトランザクション ID です。

idle in transaction が握ったまま

アプリやツールが BEGIN → UPDATE のあとコミットせずに止まると、その接続は idle in transaction になり、ロックを持ち続けます。

 pid  |        state        | wait_event_type |  wait_event   | blocked_by |  query
------+---------------------+-----------------+---------------+------------+-----------------------------------
 1837 | idle in transaction | Client          | ClientRead    | {}         | UPDATE acct SET bal=0 WHERE id=3;
 1842 | active              | Lock            | transactionid | {1837}     | ... UPDATE acct SET bal=1 WHERE id=3;
止めている側に実行 戻り値 ロック
pg_cancel_backend(1837) t 外れない(1842 は待ったまま)
pg_terminate_backend(1837) t 外れる。1837 はロールバックされ、1842 の UPDATE が通った

pg_cancel_backend は実行中の文を止める関数です。t が返るので効いたように見えます。切られた側のクライアントには FATAL: terminating connection due to administrator command が出ます。2つの関数の違いは 実行中のクエリを止める記事に書きました。

どれだけ放置されているかは now() - state_change で見ます。繰り返し起きるなら idle_in_transaction_session_timeout(既定 0=無効)で自動的に切れるようにできます。

ALTER TABLE の後ろに SELECT が並ぶ

開いたままのトランザクション R が表を SELECT しただけの状態で ALTER TABLE を実行し、さらに別の接続 S から SELECT しました。

 application_name |        state        | wait_event_type | wait_event | blocked_by
------------------+---------------------+-----------------+------------+------------
 R                | idle in transaction | Client          | ClientRead | {}
 DDL              | active              | Lock            | relation   | {R の pid}
 S                | active              | Lock            | relation   | {DDL の pid}

ALTER TABLE は AccessExclusiveLock を待ち、その後ろで S の AccessShareLock も待たされました。pg_blocking_pids を1段だけ見ると ALTER TABLE を止めればよいように見えますが、元は R です。blocked_by を空の行までたどります。

ALTER TABLE に SET lock_timeout = '2s' を付けて実行すると、2秒で ERROR: canceling statement due to lock timeout になりました。待ち続けて後ろに SELECT を溜めることはなくなるので、失敗したら時間をおいてやり直します。ALTER TABLE の書き方は ALTER TABLE の記事にあります。

待つ時間に上限を付ける

設定 既定 ロック待ちで返るエラー
lock_timeout 0(無期限) canceling statement due to lock timeout
statement_timeout 0(無期限) canceling statement due to statement timeout

statement_timeout は文の実行時間全体の上限なので、ロック待ちでなくても長い文は止まります。ロック待ちだけを切りたいときは lock_timeout を使います。

あとから調べるために log_lock_waits を有効にする

log_lock_waits は既定で off です。on にすると、deadlock_timeout(既定 1s)を超えて待った文がサーバーログに残ります。

ALTER SYSTEM SET log_lock_waits = on;
SELECT pg_reload_conf();
LOG:  process 1934 still waiting for ShareLock on transaction 1225 after 1000.303 ms
DETAIL:  Process holding the lock: 1930. Wait queue: 1934.
CONTEXT:  while updating tuple (0,4) in relation "acct"
STATEMENT:  UPDATE acct SET bal=bal+10 WHERE id=4;
LOG:  process 1934 acquired ShareLock on transaction 1225 after 2996.255 ms

DETAIL に止めていた側の pid が出るので、同じ時刻の pg_stat_activity の記録があれば突き合わせられます。再起動は要りません。デッドロックになった場合は deadlock detected の記事を見てください。

関連: PostgreSQLで実行中のクエリを止める / PostgreSQLのALTER TABLE / PostgreSQLの「deadlock detected」 / MySQLのERROR 1205

PostgreSQL をもっと深く知るための本

PostgreSQL実践入門 ──アーキテクチャ、運用監視、性能改善

技術評論社・2026年2月

アーキテクチャから運用監視・性能改善までを扱う。付録に PostgreSQL 18 の新機能と設定項目の差分がある。

Amazonで見る

[改訂3版]内部構造から学ぶPostgreSQL ―設計・運用計画の鉄則

技術評論社・2022年11月・PostgreSQL 14 ベース

内部構造の理解から、設計と運用計画の立て方を学ぶ。

Amazonで見る

Amazonのアソシエイトとして、FIRE SE は適格販売により収入を得ています。