web-dev-qa-db-fra.com

Comment détecter une requête qui détient le verrou dans Postgres?

Je veux suivre les verrous mutuels dans postgres constamment.

Je suis tombé sur Locks Monitoring article et j'ai essayé de lancer la requête suivante:

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;

Malheureusement, il ne retourne jamais un jeu de résultats non vide. Si je simplifie la requête donnée au formulaire suivant:

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;

il renvoie ensuite les requêtes en attente d’acquérir un verrou. Mais je n'arrive pas à le changer pour qu'il puisse renvoyer à la fois des requêtes bloquées et bloquantes.

Des idées?

23
Roman

Depuis la version 9.6, cela est beaucoup plus facile car il a introduit la fonction pg_blocking_pids() pour rechercher les sessions bloquant une autre session.

Donc, vous pouvez utiliser quelque chose comme ceci:

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;
31

De cet excellent article sur les verrous de requête dans Postgres , on peut obtenir une requête bloquée et une requête bloquante ainsi que leurs informations à partir de la requête suivante. 

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;

Comme la requête est longue mais utile, l'auteur de l'article a créé une vue afin de simplifier son utilisation.

19
Devi

Une chose qui me manque souvent est la possibilité de rechercher des verrous de rangée. Au moins sur les plus grandes bases de données sur lesquelles j'ai travaillé, les verrous de lignes ne sont pas affichés dans pg_locks (s'ils le étaient, pg_locks serait beaucoup, beaucoup plus grand et il n'y aurait pas de type de données réel pour montrer correctement la ligne verrouillée dans cette vue).

Je ne sais pas s'il existe une solution simple à ce problème, mais ce que je fais habituellement, c'est regarder la table où le verrou est en attente et rechercher les lignes dont le xmax est inférieur à l'identifiant de transaction présent à cet endroit. Cela me donne généralement un point de départ, mais c’est un peu pratique et pas facile à automatiser.

Notez que vous voyez les écritures non validées sur les lignes de ces tables. Une fois validées, les lignes ne sont pas visibles dans l'instantané actuel. Mais pour les grandes tables, c'est pénible.

1
Chris Travers

Cette modification de la réponse de a_horse_with_no_name vous donnera les requêtes bloquantes en plus des sessions bloquées:

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));
0
jpmc26