Reputation: 66156
I want to track mutual locks in postgres constantly.
I came across Locks Monitoring article and tried to run the following query:
SELECT bl.pid AS blocked_pid,
a.usename AS blocked_user,
kl.pid AS blocking_pid,
ka.usename AS blocking_user,
a.query AS blocked_statement
FROM pg_catalog.pg_locks bl
JOIN pg_catalog.pg_stat_activity a ON a.pid = bl.pid
JOIN pg_catalog.pg_locks kl ON kl.transactionid = bl.transactionid AND kl.pid != bl.pid
JOIN pg_catalog.pg_stat_activity ka ON ka.pid = kl.pid
WHERE NOT bl.granted;
Unfortunately, it never returns non-empty result set. If I simplify given query to the following form:
SELECT bl.pid AS blocked_pid,
a.usename AS blocked_user,
a.query AS blocked_statement
FROM pg_catalog.pg_locks bl
JOIN pg_catalog.pg_stat_activity a ON a.pid = bl.pid
WHERE NOT bl.granted;
then it returns queries which are waiting to acquire a lock. But I cannot manage to change it so that it can return both blocked and blocker queries.
Any ideas?
Upvotes: 210
Views: 439199
Reputation: 5443
From this excellent article on query locks in Postgres, one can get blocked query and blocker query and their information from the following query.
CREATE VIEW lock_monitor AS(
SELECT
COALESCE(blockingl.relation::regclass::text,blockingl.locktype) as locked_item,
now() - blockeda.query_start AS waiting_duration, blockeda.pid AS blocked_pid,
blockeda.query as blocked_query, blockedl.mode as blocked_mode,
blockinga.pid AS blocking_pid, blockinga.query as blocking_query,
blockingl.mode as blocking_mode
FROM pg_catalog.pg_locks blockedl
JOIN pg_stat_activity blockeda ON blockedl.pid = blockeda.pid
JOIN pg_catalog.pg_locks blockingl ON(
( (blockingl.transactionid=blockedl.transactionid) OR
(blockingl.relation=blockedl.relation AND blockingl.locktype=blockedl.locktype)
) AND blockedl.pid != blockingl.pid)
JOIN pg_stat_activity blockinga ON blockingl.pid = blockinga.pid
AND blockinga.datid = blockeda.datid
WHERE NOT blockedl.granted
AND blockinga.datname = current_database()
);
SELECT * from lock_monitor;
As the query is long but useful, the article author has created a view for it to simplify its usage.
Upvotes: 101
Reputation: 10226
How to show all blocked queries.
select pid,
usename,
pg_blocking_pids(pid) as blocked_by,
query as blocked_query
from pg_stat_activity
where cardinality(pg_blocking_pids(pid)) > 0;
You can kill a blocked query by using the below command.
SELECT pg_cancel_backend(a.pid), pg_terminate_backend(a.pid);
You can terminate all blocked queries using it.
SELECT pg_cancel_backend(a.pid), pg_terminate_backend(a.pid)
FROM( select pid,
usename,
pg_blocking_pids(pid) as blocked_by,
query as blocked_query
from pg_stat_activity
where cardinality(pg_blocking_pids(pid)) > 0) a
Upvotes: 32
Reputation: 71
Others have already answered your query, but I had certain situation: There were lots of queries blocking each other and I wanted to find the main blocker sessions for each blocked session.
First example: session 5 was blocked by session 4 and 4 was waiting for 3 and 2 while 3 and 2 were waiting for session 1.
5->4->{3,2}->1
In this case session 1 is real problem, once it is cleared, others will take care of themselves.
So I wanted a query which will show me results like this:
Blocked pid | Blocker pid |
---|---|
5 | 1 |
4 | 1 |
3 | 1 |
2 | 1 |
So if there is any chain locked sessions, this query will show you the main blocker sessions for each blocked session.
;with recursive
find_the_source_blocker as (
select pid
,pid as blocker_id
from pg_stat_activity pa
where pa.state<>'idle'
and array_length(pg_blocking_pids(pa.pid), 1) is null
union all
select
t.pid as pid
,f.blocker_id as blocker_id
from find_the_source_blocker f
join ( SELECT
act.pid,
blc.pid AS blocker_id
FROM pg_stat_activity AS act
LEFT JOIN pg_stat_activity AS blc ON blc.pid = ANY(pg_blocking_pids(act.pid))
where act.state<>'idle') t on f.pid=t.blocker_id
)
select distinct
s.pid
,s.blocker_id
,pb.usename as blocker_user
,pb.query_start as blocker_start
,pb.query as blocker_query
,pt.query_start as trans_start
,pt.query as trans_query
from find_the_source_blocker s
join pg_stat_activity pb on s.blocker_id=pb.pid
join pg_stat_activity pt on s.pid=pt.pid
where s.pid<>s.blocker_id
Upvotes: 7
Reputation: 29866
This modification of a_horse_with_no_name's answer will give you the last (or current, if it's still actively running) query of the blocking session in addition to just the blocked sessions:
SELECT
activity.pid,
activity.usename,
activity.query,
blocking.pid AS blocking_id,
blocking.query AS blocking_query
FROM pg_stat_activity AS activity
JOIN pg_stat_activity AS blocking ON blocking.pid = ANY(pg_blocking_pids(activity.pid));
This helps you understand which operations are interfering with each other (even if the block comes from a previous query), helping you understand the impact of killing one session and figuring out how to prevent blocking in the future.
Upvotes: 49
Reputation: 128
for postgresql versions earlier than postgresql 9.6 which does not have pg_blocking_pids
function,you can use following query to find blocked query and blocking query.
SELECT w.query AS waiting_query,
w.pid AS waiting_pid,
w.usename AS waiting_user,
now() - w.query_start AS waiting_duration,
l.query AS locking_query,
l.pid AS locking_pid,
l.usename AS locking_user,
t.schemaname || '.' || t.relname AS tablename,
now() - l.query_start AS locking_duration
FROM pg_stat_activity w
JOIN pg_locks l1 ON w.pid = l1.pid AND NOT l1.granted
JOIN pg_locks l2 ON l1.relation = l2.relation AND l2.granted
JOIN pg_stat_activity l ON l2.pid = l.pid
JOIN pg_stat_user_tables t ON l1.relation = t.relid
WHERE w.waiting;
Upvotes: 9
Reputation: 334
Postgres has a very rich system catalog exposed via SQL tables. PG's statistics collector is a subsystem that supports collection and reporting of information about server activity.
Now to figure out the blocking PIDs you can simply query pg_stat_activity
.
select pg_blocking_pids(pid) as blocked_by
from pg_stat_activity
where cardinality(pg_blocking_pids(pid)) > 0;
To, get the query corresponding to the blocking PID, you can self-join or use it as a where clause in a subquery.
SELECT query
FROM pg_stat_activity
WHERE pid IN (select unnest(pg_blocking_pids(pid)) as blocked_by from pg_stat_activity where cardinality(pg_blocking_pids(pid)) > 0);
Note: Since pg_blocking_pids(pid)
returns an Integer[], so you need to unnest
it before you use it in a WHERE pid IN
clause.
Hunting for slow queries can be tedious sometimes, so have patience. Happy hunting.
Upvotes: 11
Reputation:
Since 9.6 this is a lot easier as it introduced the function pg_blocking_pids()
to find the sessions that are blocking another session.
So you can use something like this:
select pid,
usename,
pg_blocking_pids(pid) as blocked_by,
query as blocked_query
from pg_stat_activity
where cardinality(pg_blocking_pids(pid)) > 0;
Upvotes: 506
Reputation: 26454
One thing I find that is often missing from these is an ability to look up row locks. At least on the larger databases I have worked on, row locks are not shown in pg_locks (if they were, pg_locks would be much, much larger and there isn't a real data type to show the locked row in that view properly).
I don't know that there is a simple solution to this but usually what I do is look at the table where the lock is waiting and search for rows where the xmax is less than the transaction id present there. That usually gives me a place to start, but it is a bit hands-on and not automation friendly.
Note that shows you uncommitted writes on rows on those tables. Once committed, the rows are not visible in the current snapshot. But for large tables, that is a pain.
Upvotes: 4