Created
May 17, 2021 14:39
-
-
Save gtors/239d57ac7b6008ae9c395ed58f299bb1 to your computer and use it in GitHub Desktop.
Postgres "lock trees" analysis: the forest of trees of blocked and blocking sessions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- https://gitlab.com/-/snippets/1890428 | |
-- The query shows "the forest of trees" of blocked and blocking sessions with the corresponding queries. It helps to understand, which session is being blocked by which one, and what is "the depth" of the lock chain. | |
-- Use it to find the "roots" of trees, then use pg_cancel_backend(pid) or pg_terminate_backend(pid) to get rid of blockers and release the locks. | |
-- Derived from DataEgret's locktree.sql (https://github.com/dataegret/pg-utils/blob/master/sql/locktree.sql) | |
\timing on | |
set statement_timeout to '2s'; | |
with recursive l as ( | |
select | |
pid, locktype, granted, | |
array_position(array['AccessShare','RowShare','RowExclusive','ShareUpdateExclusive','Share','ShareRowExclusive','Exclusive','AccessExclusive'], left(mode, -4)) m, | |
row(locktype, database, relation, page, tuple, virtualxid, transactionid, classid, objid, objsubid) obj | |
from pg_locks | |
), pairs as ( | |
select w.pid waiter, l.pid locker, l.obj, l.m | |
from l w join l on l.obj is not distinct from w.obj and l.locktype = w.locktype and not l.pid = w.pid and l.granted | |
where not w.granted | |
and not exists (select from l i where i.pid=l.pid and i.locktype = l.locktype and i.obj is not distinct from l.obj and i.m > l.m) | |
), leads as ( | |
select o.locker, 1::int lvl, count(*) q, array[locker] track, false as cycle | |
from pairs o | |
group by o.locker | |
union all | |
select i.locker, leads.lvl + 1, (select count(*) from pairs q where q.locker = i.locker), leads.track || i.locker, i.locker = any(leads.track) | |
from pairs i, leads | |
where i.waiter=leads.locker and not cycle | |
), tree as ( | |
select locker pid,locker dad,locker root,case when cycle then track end dl, null::record obj,0 lvl, locker::text path, array_agg(locker) over () all_pids | |
from leads o | |
where | |
(cycle and not exists (select from leads i where i.locker=any(o.track) and (i.lvl>o.lvl or i.q<o.q))) | |
or (not cycle and not exists (select from pairs where waiter=o.locker) and not exists (select from leads i where i.locker=o.locker and i.lvl>o.lvl)) | |
union all | |
select w.waiter pid,tree.pid,tree.root,case when w.waiter=any(tree.dl) then tree.dl end,w.obj,tree.lvl+1,tree.path||'.'||w.waiter,all_pids || array_agg(w.waiter) over () | |
from tree | |
join pairs w on tree.pid=w.locker and not w.waiter = any (all_pids) | |
) | |
select (clock_timestamp() - a.xact_start)::interval(0) as transaction_age, | |
(clock_timestamp() - a.state_change)::interval(0) as change_age, | |
a.datname, | |
a.usename, | |
a.client_addr, | |
--w.obj wait_on_object, | |
tree.pid, | |
--(select array_to_json(array_agg(json_build_object(mode, granted))) from pg_locks pl where pl.pid = tree.pid) as locks, | |
a.wait_event_type, | |
a.wait_event, | |
pg_blocking_pids(tree.pid) blocked_by_pids, | |
replace(a.state, 'idle in transaction', 'idletx') state, | |
lvl, | |
(select count(*) from tree p where p.path ~ ('^'||tree.path) and not p.path=tree.path) blocking_others, | |
case when tree.pid=any(tree.dl) then '!>' else repeat(' .', lvl) end||' '||trim(left(regexp_replace(a.query, e'\\s+', ' ', 'g'),300)) latest_query_in_tx | |
from tree | |
left join pairs w on w.waiter = tree.pid and w.locker = tree.dad | |
join pg_stat_activity a using (pid) | |
join pg_stat_activity r on r.pid=tree.root | |
order by (now() - r.xact_start), path | |
\watch 1 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment