Skip to content

Instantly share code, notes, and snippets.

@gtors
Created May 17, 2021 14:39
Show Gist options
  • Save gtors/239d57ac7b6008ae9c395ed58f299bb1 to your computer and use it in GitHub Desktop.
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
-- 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