Documentation PostgreSQL 15.15 » Annexes » Modules supplémentaires fournis » pg_stat_statements

Précédent | Niveau supérieur | Suivant
F.31. pgrowlocks | Sommaire | F.33. pgstattuple

## F.32. pg_stat_statements

Le module `pg_stat_statements` fournit un moyen de surveiller les statistiques d'optimisation et d'exécution de tous les ordres SQL exécutés par un serveur.

Le module doit être chargé par l'ajout de `pg_stat_statements` à shared_preload_libraries dans le fichier de configuration `postgresql.conf` parce qu'il a besoin de mémoire partagée supplémentaire. Ceci signifie qu'il faut redémarrer le serveur pour ajouter ou supprimer le module. De plus, le calcul de l'identifiant de requête doit être activé pour que le module soit actif, ce qui est fait automatiquement si compute_query_id est configuré à `auto` ou `on`, ou tout autre module tiers chargé qui calcule les identifiants de requête.

Quand `pg_stat_statements` est actif, il récupère des statistiques sur toutes les bases de données du serveur. Pour y accéder et les manipuler, le module fournit les vues `pg_stat_statements` et `pg_stat_statements_info`, et les fonctions `pg_stat_statements_reset` et `pg_stat_statements`. Elles ne sont pas disponibles globalement mais peuvent être activées pour une base de données spécifique avec l'instruction `CREATE EXTENSION pg_stat_statements`.

### F.32.1. La vue `pg_stat_statements`

Les statistiques collectées par le module sont rendues disponibles par une vue nommée `pg_stat_statements`. Cette vue contient une ligne pour chaque combinaison d'un identifiant de base de données, d'un identifiant utilisateur, d'un identifiant de requête et s'il s'agit d'une requête principale ou non (jusqu'au nombre maximum d'ordres distincts que le module peut surveiller). Les colonnes de la vue sont affichées dans Tableau F.20.

Tableau F.20. Colonnes de `pg_stat_statements`

Type de colonne Description
`userid` `oid` (référence `pg_authid`.`oid`) OID de l'utilisateur qui a exécuté la requête
`dbid` `oid` (référence `pg_database`.`oid`) OID de la base dans laquelle a été exécutée la requête
`toplevel` `bool` True si la requête a été exécutée comme instruction de haut niveau (toujours true si `pg_stat_statements.track` est configuré à `top`)
`queryid` `bigint` Code de hachage, calculé à partir de l'arbre d'analyse de la requête
`query` `text` Texte d'une requête représentative
`plans` `bigint` Nombre d'optimisations de la requête (si `pg_stat_statements.track_planning` est activé, sinon zéro)
`total_plan_time` `double precision` Durée totale passée à optimiser la requête, en millisecondes (si `pg_stat_statements.track_planning` est activé, sinon zéro)
`min_plan_time` `double precision` Durée minimale passée à optimiser la requête, en millisecondes (si `pg_stat_statements.track_planning` est activé, sinon zéro)
`max_plan_time` `double precision` Durée maximale passée à optimiser la requête, en millisecondes (si `pg_stat_statements.track_planning` est activé, sinon zéro)
`mean_plan_time` `double precision` Durée moyenne passée à optimiser la requête, en millisecondes (si `pg_stat_statements.track_planning` est activé, sinon zéro)
`stddev_plan_time` `double precision` Déviation standard de la durée passée à optimiser la requête, en millisecondes (si `pg_stat_statements.track_planning` est activé, sinon zéro)
`calls` `bigint` Nombre d'exécutions de la requête
`total_exec_time` `double precision` Durée totale passée à exécuter la requête, en millisecondes
`min_exec_time` `double precision` Durée minimale passée à exécuter la requête, en millisecondes
`max_exec_time` `double precision` Durée maximale passée à exécuter la requête, en millisecondes
`mean_exec_time` `double precision` Durée moyenne passée à exécuter la requête, en millisecondes
`stddev_exec_time` `double precision` Déviation standard de la durée passée à exécuter la requête, en millisecondes
`rows` `bigint` Nombre total de lignes récupérées ou affectées par la requête
`shared_blks_hit` `bigint` Nombre total de blocs lus dans le cache partagé par la requête
`shared_blks_read` `bigint` Nombre total de blocs lus hors cache partagé par la requête
`shared_blks_dirtied` `bigint` Nombre total de blocs modifiés dans le cache partagé par la requête
`shared_blks_written` `bigint` Nombre total de blocs du cache partagé écrit sur disque par la requête
`local_blks_hit` `bigint` Nombre total de blocs lus dans le cache local par la requête
`local_blks_read` `bigint` Nombre total de blocs lus hors du cache local par la requête
`local_blks_dirtied` `bigint` Nombre total de blocs modifiés dans le cache local par la requête
`local_blks_written` `bigint` Nombre total de blocs du cache local écrit sur disque par la requête
`temp_blks_read` `bigint` Nombre total de blocs lus dans les fichiers temporaires par la requête
`temp_blks_written` `bigint` Nombre total de blocs écrits dans les fichiers temporaires par la requête
`blk_read_time` `double precision` Durée totale de lecture des blocs des fichiers de données (hors cache) par la requête, en millisecondes (si track_io_timing est activé, sinon zéro)
`blk_write_time` `double precision` Durée totale de l'écriture des blocs des fichiers de données (hors cache) par la requête, en millisecondes (si track_io_timing est activé, sinon zéro)
`temp_blk_read_time` `double precision` Durée totale des lectures des blocs de fichiers temporaires en millisecondes (si track_io_timing est activé, sinon zéro)
`temp_blk_write_time` `double precision` Durée totale des écritures des blocs de fichiers temporaires en millisecondes (si track_io_timing est activé, sinon zéro)
`wal_records` `bigint` Nombre total d'enregistrements générés dans les WAL par la requête
`wal_fpi` `bigint` Nombre total d'images complètes de blocs (full page images) générés dans les WAL par la requête
`wal_bytes` `numeric` Nombre total d'octets générés dans les WAL par la requête
`jit_functions` `bigint` Nombre total de fonctions compilées par JIT pour cette requête
`jit_generation_time` `double precision` Durée totale passée par la requête sur la génération de code JIT, en millisecondes
`jit_inlining_count` `bigint` Nombre de fois où les fonctions ont été incluses
`jit_inlining_time` `double precision` Durée totale passée par la requête sur l'inclusion de fonctions, en millisecondes
`jit_optimization_count` `bigint` Nombre de fois où la requête a été optimisée
`jit_optimization_time` `double precision` Durée totale passée sur l'optimisation de la requête, en millisecondes
`jit_emission_count` `bigint` Nombre de fois où du code a été émis
`jit_emission_time` `double precision` Durée totale passée par la requête sur de l'émission de code, en millisecondes

Pour raisons de sécurité, seuls les superutilisateurs et les rôles disposant des droits du rôle `pg_read_all_stats` sont autorisé à voir le texte SQL ainsi que le champ `queryid` des requêtes exécutées par d'autres utilisateurs. Les autres utilisateurs peuvent cependant voir les statistiques, si la vue a été installée dans leur base de données.

Les requêtes qui disposent d'un plan d'exécution (c'est-à-dire `SELECT`, `INSERT`, `UPDATE`, `DELETE` et `MERGE`) sont combinées en une entrée unique dans `pg_stat_statements` lorsqu'elles ont un plan d'exécution similaire (d'après leur hachage). En substance, cela signifie que deux requêtes seront considérées comme équivalentes si elles sont sémantiquement les mêmes mais disposent de valeurs littérales différentes dans la requête. Les requêtes utilitaires (c'est-à-dire toutes les autres) ne sont considérées comme unique que lorsqu'elles sont égales au caractère près.

### Note

Les détails suivant sur le remplacement des constantes et le `queryid` s'appliquent seulement si compute_query_id est activé. Si vous utilisez un module externe pour calculer `queryid`, vous devez vous référer à sa documentation pour les détails.

Quand la valeur d'une constante a été ignorée pour pouvoir comparer la requête à d'autres requêtes, la constante est remplacée par un symbole de paramètre, tel que `$1`, dans l'affichage de `pg_stat_statements`. Le reste du texte de la requête est tel qu'était la première requête ayant la valeur de hashage `queryid` spécifique associée à l'entrée dans `pg_stat_statements`.

Dans certains cas, les requêtes SQL avec des textes différents peuvent être fusionnés en une seule entrée `pg_stat_statements`. Normalement, cela n'arrive que pour les requêtes dont la sémantique est équivalente, mais il y a une petite chance que des collisions de l'algorithme de hachage aient pour conséquence la fusion de requêtes sans rapport en une entrée. (Cela ne peut cependant pas arriver pour des requêtes appartenant à des utilisateurs différents ou des bases de données différentes).

Puisque la valeur de hachage `queryid` est calculée sur la représentation de la requête après analyse, l'inverse est également possible : des requêtes avec un texte identique peuvent apparaître comme des entrées séparées, si elles ont des significations différentes en fonction de facteurs externes, comme des réglages de `search_path` différents.

Les programmes utilisant `pg_stat_statements` pourraient préférer utiliser `queryid` (peut-être en association avec `dbid` et `userid`) pour disposer d'un identifiant plus stable et plus sûr pour chaque entrée plutôt que le texte de la requête. Cependant, il est important de comprendre qu'il n'y a qu'une garantie limitée sur la stabilité de la valeur de hachage de `queryid`. Puisque l'identifiant est dérivé de l'arbre après analyse, sa valeur est une fonction, entre autres choses, des identifiants d'objet interne apparaissant dans cette représentation. Cela a des implications paradoxales. Par exemple, `pg_stat_statements` considérera deux requêtes apparemment identiques comme distinctes, si elles référencent une table qui a été supprimée et recréée entre l'exécution de ces deux requêtes. Le processus de hachage est également sensible aux différences d'architecture des machines ainsi que d'autres facettes de la plateforme. De plus, il n'est pas sûr de partir du principe que `queryid` restera stable entre des versions majeures de PostgreSQL.

Deux serveurs participant à une réplication basée sur le rejeu physique des journaux de transactions devraient avoir des valeurs de `queryid` identiques pour la même requête. Cependant, les systèmes de réplication logique ne promettent pas de conserver des réplicas identiques surtout les détails intéressants, donc `queryid` ne sera pas un identifiant utile pour accumuler les coûts dans un ensemble de réplicas logiques. En cas de doute, un test direct est recommandé.

En règle général, il peut être supposé que les valeurs `queryid` sont stables entre des versions mineures de PostgreSQL, en supposant que les instances sont exécutées sur la même architecture matérielle et que les détails de métadonnées du catalogue correspondent. La compatibilité entre versions mineures ne sera cassée qu'en cas de dernière extrémité.

Le symbole de paramètre utilité pour remplacer les constantes dans le texte représentatif de la requête démarre après le plus grand paramètre `$``n` dans le texte de la requête originale, ou `$1` s'il n'y en avait pas. Il est intéressant de noter que dans certains cas il pourrait y avoir un symbole de paramètre caché qui affecte cette numérotation. Par exemple, PL/pgSQL utilise des symbole de paramètre cachés pour insérer des valeurs de variables locales à la fonction dans les requêtes, ainsi un ordre PL/pgSQL comme `SELECT i + 1 INTO j` aurait un texte représentatif tel que `SELECT i + $2`.

Les textes des requêtes sont conservées dans un fichier texte externe et ne consomment pas de mémoire partagée. De ce fait, même les textes très longs de requêtes peuvent être enregistrés avec succès. Néanmoins, si beaucoup de textes très longs de requêtes sont accumulées, le fichier externe peut devenir suffisamment gros pour ne plus être gérable. Si cela survient, comme méthode de restauration, `pg_stat_statements` peut choisir d'ignorer les textes de requêtes. Dans ce cas, le champ `query` apparaitra vide sur les lignes de la vue `pg_stat_statements` mais les statistiques associées seront préservées. Si cela arrive, réfléchissez à réduire la valeur du paramètre `pg_stat_statements.max` pour empêcher que cela ne recommence.

`plans` et `calls` peuvent différer car les statistiques d'optimisation/planification et d'exécution sont mises à jours à leur fin respective, et seulement si elles ont réussi. Par exemple, si une requête est optimisée avec succès mais échoue sur la phase d'exécution, seules les statistiques d'optimisation/planification seront modifiées. Si l'optimisation/planification est ignorée parce qu'un plan en cache est utilisé, seules les statistiques d'exécution seront mises à jour.

### F.32.2. La vue `pg_stat_statements_info`

Les statistiques du module `pg_stat_statements` lui-même sont tracées et rendues disponibles via une vue nommée `pg_stat_statements_info`. Cette vue contient une seule ligne. Les colonnes de la vue sont affichées dans Tableau F.21.

Tableau F.21. Colonnes de `pg_stat_statements_info`

Type de colonne Description
`dealloc` `bigint` Nombre total de fois où les enregistrements de `pg_stat_statements` pour les requêtes les moins exécutées ont été désallouées parce que plus de `pg_stat_statements.max` requêtes distinctes ont été observées
`stats_reset` `timestamp with time zone` Horodatage de la dernière réinitialisation de toutes les statistiques de `pg_stat_statements`.

### F.32.3. Fonctions

`pg_stat_statements_reset(userid Oid, dbid Oid, queryid bigint) returns void`

  `pg_stat_statements_reset` annule les statistiques récupérées jusqu'à maintenant par `pg_stat_statements` correspondant aux `userid`, `dbid` et `queryid` indiqués. Si un de ces paramètres n'est pas spécifié, la valeur par défaut `0` (invalide) est utilisée pour chacun d'entre eux et les statistiques qui correspondent avec les autres paramètres seront réinitialisées. Si aucun paramètre n'est spécifié ou si tous les paramètres spécifiés sont à `0` (invalide), elle annulera toutes les statistiques. Si toutes les statistiques de la vue `pg_stat_statements` sont annulées, cela va aussi réinitialiser les statistiques de la vue `pg_stat_statements_info`. Par défaut, cette fonction peut seulement être exécutée par les superutilisateurs. L'accès peut être donné à d'autres utilisateurs en utilisant la commande `GRANT`.

`pg_stat_statements(showtext boolean) returns setof record`

  La vue `pg_stat_statements` est basée sur une fonction également nommée `pg_stat_statements`. Les clients peuvent appeler la fonction `pg_stat_statements` directement, et peuvent en spécifiant `showtext := false` ne pas récupérer le texte de la requête (ce qui veut dire que l'argument `OUT` qui correspond à la colonne `query` de la vue retournera des NULL). Cette fonctionnalité est prévue pour le support d'outils externes qui pourraient vouloir éviter le surcoût de récupérer de manière répétée les textes des requêtes de longueur indéterminées. De tels outils peuvent à la place eux-même mettre le premier texte de requête récupéré pour chaque entrée, puisque c'est déjà ce que fait `pg_stat_statements` lui-même, et ensuite récupérer les textes de requêtes uniquement si nécessaire. Puisque le serveur stocke les textes de requête dans un fichier, cette approche pourrait réduire les entrée/sorties physiques pour des vérifications répétées des données de `pg_stat_statements`.

### F.32.4. Paramètres de configuration

`pg_stat_statements.max` (`integer`)

  `pg_stat_statements.max` est le nombre maximum d'ordres tracés par le module (c'est-à-dire le nombre maximum de lignes dans la vue `pg_stat_statements`). Si un nombre supérieur d'ordres SQL distincts a été observé, c'est l'information sur les ordres les moins exécutés qui est ignorée. Le nombre de fois où une telle information est ignorée est consultable dans la vu `pg_stat_statements_info`. La valeur par défaut est 5000. Ce paramètre peut uniquement être positionné au démarrage du serveur.

`pg_stat_statements.track` (`enum`)

  `pg_stat_statements.track` contrôle quels sont les ordres comptabilisés par le module. Spécifiez `top` pour suivre les ordres de plus haut niveau (ceux qui sont soumis directement par les clients), `all` pour suivre également les ordres imbriqués (tels que les ordres invoqués dans les fonctions) ou `none` pour désactiver la récupération des statistiques sur les requêtes. La valeur par défaut est `top`. Seuls les superutilisateurs peuvent changer ce paramétrage.

`pg_stat_statements.track_utility` (`boolean`)

  `pg_stat_statements.track_utility` contrôle si les commandes utilitaires sont tracées par le module. Les commandes utilitaires sont toutes les commandes SQL sauf `SELECT`, `INSERT`, `UPDATE`, `DELETE` et `MERGE`. La valeur par défaut est `on`. Seuls les superutilisateurs peuvent modifier cette configuration.

`pg_stat_statements.track_planning` (`boolean`)

  `pg_stat_statements.track_planning` contrôle si les opérations d'optimisation/planification et leur durée sont tracées par ce module. Activer ce paramètre pourrait résulter en une perte visible de performance, spécialement quand les requêtes avec des structures identiques sont exécutées par de nombreuses connexions concurrentes, ce qui pousse à mettre à jour un petit nombre d'entrées de `pg_stat_statements`. La valeur par défaut est `off`. Seuls les superutilisateurs peuvent modifier cette configuration.

`pg_stat_statements.save` (`boolean`)

  `pg_stat_statements.save` précise s'il faut sauvegarder les statistiques lors des arrêts du serveur. S'il est `off`, alors les statistiques ne sont pas sauvegardées lors de l'arrêt ni rechargées au démarrage du serveur. La valeur par défaut est `on`. Ce paramètre peut uniquement être positionné dans le fichier `postgresql.conf` ou sur la ligne de commande du serveur.

Le module a besoin de mémoire partagée supplémentaire proportionnelle à `pg_stat_statements.max`. Notez que cette mémoire est consommée quand le module est chargé, même si `pg_stat_statements.track` est positionné à `none`.

Ces paramètres doivent être définis dans `postgresql.conf`. Un usage courant pourrait être :

    # postgresql.conf
    shared_preload_libraries = 'pg_stat_statements'

    compute_query_id = on
    pg_stat_statements.max = 10000
    pg_stat_statements.track = all

### F.32.5. Exemple de sortie

    bench=# SELECT pg_stat_statements_reset();

    $ pgbench -i bench
    $ pgbench -c10 -t300 bench

    bench=# \x
    bench=# SELECT query, calls, total_exec_time, rows, 100.0 * shared_blks_hit /
                   nullif(shared_blks_hit + shared_blks_read, 0) AS hit_percent
              FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 5;

    -[ RECORD 1 ]---+--------------------------------------------------​------------------
    query           | UPDATE pgbench_branches SET bbalance = bbalance + $1 WHERE bid = $2
    calls           | 3000
    total_exec_time | 25565.855387
    rows            | 3000
    hit_percent     | 100.0000000000000000
    -[ RECORD 2 ]---+--------------------------------------------------​------------------
    query           | UPDATE pgbench_tellers SET tbalance = tbalance + $1 WHERE tid = $2
    calls           | 3000
    total_exec_time | 20756.669379
    rows            | 3000
    hit_percent     | 100.0000000000000000
    -[ RECORD 3 ]---+--------------------------------------------------​------------------
    query           | copy pgbench_accounts from stdin
    calls           | 1
    total_exec_time | 291.865911
    rows            | 100000
    hit_percent     | 100.0000000000000000
    -[ RECORD 4 ]---+--------------------------------------------------​------------------
    query           | UPDATE pgbench_accounts SET abalance = abalance + $1 WHERE aid = $2
    calls           | 3000
    total_exec_time | 271.232977
    rows            | 3000
    hit_percent     | 98.8454011741682975
    -[ RECORD 5 ]---+--------------------------------------------------​------------------
    query           | alter table pgbench_accounts add primary key (aid)
    calls           | 1
    total_exec_time | 160.588563
    rows            | 0
    hit_percent     | 100.0000000000000000

    bench=# SELECT pg_stat_statements_reset(0,0,s.queryid) FROM pg_stat_statements AS s
                WHERE s.query = 'UPDATE pgbench_branches SET bbalance = bbalance + $1 WHERE bid = $2';

    bench=# SELECT query, calls, total_exec_time, rows, 100.0 * shared_blks_hit /
                   nullif(shared_blks_hit + shared_blks_read, 0) AS hit_percent
              FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 5;
    -[ RECORD 1 ]---+--------------------------------------------------​------------------
    query           | UPDATE pgbench_tellers SET tbalance = tbalance + $1 WHERE tid = $2
    calls           | 3000
    total_exec_time | 20756.669379
    rows            | 3000
    hit_percent     | 100.0000000000000000
    -[ RECORD 2 ]---+--------------------------------------------------​------------------
    query           | copy pgbench_accounts from stdin
    calls           | 1
    total_exec_time | 291.865911
    rows            | 100000
    hit_percent     | 100.0000000000000000
    -[ RECORD 3 ]---+--------------------------------------------------​------------------
    query           | UPDATE pgbench_accounts SET abalance = abalance + $1 WHERE aid = $2
    calls           | 3000
    total_exec_time | 271.232977
    rows            | 3000
    hit_percent     | 98.8454011741682975
    -[ RECORD 4 ]---+--------------------------------------------------​------------------
    query           | alter table pgbench_accounts add primary key (aid)
    calls           | 1
    total_exec_time | 160.588563
    rows            | 0
    hit_percent     | 100.0000000000000000
    -[ RECORD 5 ]---+--------------------------------------------------​------------------
    query           | vacuum analyze pgbench_accounts
    calls           | 1
    total_exec_time | 136.448116
    rows            | 0
    hit_percent     | 99.9201915403032721

    bench=# SELECT pg_stat_statements_reset(0,0,0);

    bench=# SELECT query, calls, total_exec_time, rows, 100.0 * shared_blks_hit /
                   nullif(shared_blks_hit + shared_blks_read, 0) AS hit_percent
              FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 5;
    -[ RECORD 1 ]---+--------------------------------------------------​---------------------------
    query           | SELECT pg_stat_statements_reset(0,0,0)
    calls           | 1
    total_exec_time | 0.189497
    rows            | 1
    hit_percent     |
    -[ RECORD 2 ]---+--------------------------------------------------​---------------------------
    query           | SELECT query, calls, total_exec_time, rows, $1 * shared_blks_hit /          +
                    |                nullif(shared_blks_hit + shared_blks_read, $2) AS hit_percent+
                    |           FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT $3
    calls           | 0
    total_exec_time | 0
    rows            | 0
    hit_percent     |

### F.32.6. Auteurs

Takahiro Itagaki `<itagaki.takahiro@oss.ntt.co.jp>`. La normalisation des requêtes a été ajoutée par Peter Geoghegan `<peter@2ndquadrant.com>`.