Postgres: Blocking Lock PID tree
WITH RECURSIVE activity AS (
SELECT
pg_blocking_pids (pid) blocked_by,
*,
age(clock_timestamp(), xact_start)::interval(0) AS tx_age,
-- "pg_locks.waitstart" – PG14+ only; for older versions: age(clock_timestamp(), state_change) as wait_age
age(clock_timestamp(), (
SELECT
max(l.waitstart)
FROM pg_locks l
WHERE
a.pid = l.pid))::interval(0) AS wait_age
FROM
pg_stat_activity a
WHERE
state IS DISTINCT FROM 'idle'
),
blockers AS (
SELECT
array_agg(DISTINCT c ORDER BY c) AS pids
FROM (
SELECT
unnest(blocked_by)
FROM
activity) AS dt (c)
),
tree AS (
SELECT
activity.*,
1 AS level,
activity.pid AS top_blocker_pid,
ARRAY[activity.pid] AS path,
ARRAY[activity.pid]::int[] AS all_blockers_above
FROM
activity,
blockers
WHERE
ARRAY[pid] <@ blockers.pids
AND blocked_by = '{}'::int[]
UNION ALL
SELECT
activity.*,
tree.level + 1 AS level,
tree.top_blocker_pid,
path || ARRAY[activity.pid] AS path,
tree.all_blockers_above || array_agg(activity.pid) OVER () AS all_blockers_above
FROM
activity,
tree
WHERE
NOT ARRAY[activity.pid] <@ tree.all_blockers_above
AND activity.blocked_by <> '{}'::int[]
AND activity.blocked_by <@ tree.all_blockers_above
)
SELECT
pid,
blocked_by,
CASE WHEN wait_event_type <> 'Lock' THEN
replace(state, 'idle in transaction', 'idletx')
ELSE
'waiting'
END AS state,
wait_event_type || ':' || wait_event AS wait,
wait_age,
tx_age,
to_char(age(backend_xid), 'FM999,999,999,990') AS xid_age,
to_char(2147483647 - age(backend_xmin), 'FM999,999,999,990') AS xmin_ttf,
datname,
usename,
(
SELECT
count(DISTINCT t1.pid)
FROM
tree t1
WHERE
ARRAY[tree.pid] <@ t1.path
AND t1.pid <> tree.pid) AS blkd,
format('%s %s%s', lpad('[' || pid::text || ']', 9, ' '), repeat('.', level -1) || CASE WHEN level > 1 THEN
' '
END,
LEFT (query, 1000)) AS query
FROM
tree
ORDER BY
top_blocker_pid,
level,
pid;
SELECT
pg_blocking_pids (pid) blocked_by,
*,
age(clock_timestamp(), xact_start)::interval(0) AS tx_age,
-- "pg_locks.waitstart" – PG14+ only; for older versions: age(clock_timestamp(), state_change) as wait_age
age(clock_timestamp(), (
SELECT
max(l.waitstart)
FROM pg_locks l
WHERE
a.pid = l.pid))::interval(0) AS wait_age
FROM
pg_stat_activity a
WHERE
state IS DISTINCT FROM 'idle'
),
blockers AS (
SELECT
array_agg(DISTINCT c ORDER BY c) AS pids
FROM (
SELECT
unnest(blocked_by)
FROM
activity) AS dt (c)
),
tree AS (
SELECT
activity.*,
1 AS level,
activity.pid AS top_blocker_pid,
ARRAY[activity.pid] AS path,
ARRAY[activity.pid]::int[] AS all_blockers_above
FROM
activity,
blockers
WHERE
ARRAY[pid] <@ blockers.pids
AND blocked_by = '{}'::int[]
UNION ALL
SELECT
activity.*,
tree.level + 1 AS level,
tree.top_blocker_pid,
path || ARRAY[activity.pid] AS path,
tree.all_blockers_above || array_agg(activity.pid) OVER () AS all_blockers_above
FROM
activity,
tree
WHERE
NOT ARRAY[activity.pid] <@ tree.all_blockers_above
AND activity.blocked_by <> '{}'::int[]
AND activity.blocked_by <@ tree.all_blockers_above
)
SELECT
pid,
blocked_by,
CASE WHEN wait_event_type <> 'Lock' THEN
replace(state, 'idle in transaction', 'idletx')
ELSE
'waiting'
END AS state,
wait_event_type || ':' || wait_event AS wait,
wait_age,
tx_age,
to_char(age(backend_xid), 'FM999,999,999,990') AS xid_age,
to_char(2147483647 - age(backend_xmin), 'FM999,999,999,990') AS xmin_ttf,
datname,
usename,
(
SELECT
count(DISTINCT t1.pid)
FROM
tree t1
WHERE
ARRAY[tree.pid] <@ t1.path
AND t1.pid <> tree.pid) AS blkd,
format('%s %s%s', lpad('[' || pid::text || ']', 9, ' '), repeat('.', level -1) || CASE WHEN level > 1 THEN
' '
END,
LEFT (query, 1000)) AS query
FROM
tree
ORDER BY
top_blocker_pid,
level,
pid;
Comments
Post a Comment