

Comme nous l'avons vu dans la section précédente, le planificateur de requêtes a besoin d'estimer le nombre de lignes récupérées par une requête pour faire les bons choix dans ses plans de requêtes. Cette section fournit un aperçu sur les statistiques que le système utilise pour ces estimations.
    Un élément des statistiques est le nombre total d'entrées dans chaque
    table et index, ainsi que le nombre de blocs disque occupés par chaque
    table et index. Cette information est conservée dans la table pg_class sur
    les colonnes reltuples et
    relpages. Nous pouvons la regarder avec des
    requêtes comme celle-ci :
    
SELECT relname, relkind, reltuples, relpages
FROM pg_class
WHERE relname LIKE 'tenk1%';
       relname        | relkind | reltuples | relpages
----------------------+---------+-----------+----------
 tenk1                | r       |     10000 |      345
 tenk1_hundred        | i       |     10000 |       11
 tenk1_thous_tenthous | i       |     10000 |       30
 tenk1_unique1        | i       |     10000 |       30
 tenk1_unique2        | i       |     10000 |       30
(5 rows)
    Ici, nous pouvons voir que tenk1 contient 10000
    lignes, comme pour ses index, mais que les index sont bien plus petits
    que la table (ce qui n'est pas surprenant).
   
    Pour des raisons d'efficacité, les valeurs des colonnes
    reltuples et
    relpages ne sont pas mises à jour en temps
    réel, et contiennent alors souvent des valeurs un peu obsolètes. Elles
    sont mises à jour par les commandes VACUUM,
    ANALYZE et quelques commandes DDL comme
    CREATE INDEX. Une opération VACUUM
    ou ANALYZE qui ne parcourt pas la table entièrement
    (ce qui est le cas le plus fréquent) augmentera de façon incrémentale la
    valeur de reltuples sur la base de la partie
    de la table qu'elle a parcourue, résultant en une valeur approximative.
    Dans tous les cas, le planificateur mettra à l'échelle les valeurs qu'il
    aura trouvées dans pg_class pour correspondre à
    la taille physique de la table, obtenant ainsi une approximation plus
    proche de la réalité.
   
    La plupart des requêtes ne récupèrent qu'une fraction des lignes dans une
    table à cause de clauses WHERE qui restreignent les
    lignes à examiner. Du coup, le planificateur a besoin d'une estimation de
    la sélectivité des clauses
    WHERE, c'est-à-dire la fraction des lignes qui
    correspondent à chaque condition de la clause WHERE.
    L'information utilisée pour cette tâche est stockée dans le catalogue
    système pg_statistic.
    Les entrées de pg_statistic sont mises à jour
    par les commandes ANALYZE et VACUUM
    ANALYZE et sont toujours approximatives même si elles ont été
    mises à jour récemment.
   
    Plutôt que de regarder directement dans
    pg_statistic, il vaut mieux voir sa vue pg_stats lors
    d'un examen manuel des statistiques. La vue
    pg_stats est conçue pour être plus facilement
    lisible. De plus, pg_stats est lisible par tous
    alors que pg_statistic n'est lisible que par un
    superutilisateur (ceci empêche les utilisateurs sans droits d'apprendre
    certaines choses sur le contenu des tables appartenant à d'autres
    personnes à partir des statistiques. La vue
    pg_stats est restreinte pour n'afficher que les
    lignes des tables lisibles par l'utilisateur courant). Par exemple, nous
    pourrions lancer :
    
SELECT attname, inherited, n_distinct,
       array_to_string(most_common_vals, E'\n') as most_common_vals
FROM pg_stats
WHERE tablename = 'road';
 attname | inherited | n_distinct |          most_common_vals
---------+-----------+------------+------------------------------------
 name    | f         | -0.5681108 | I- 580                        Ramp+
         |           |            | I- 880                        Ramp+
         |           |            | Sp Railroad                       +
         |           |            | I- 580                            +
         |           |            | I- 680                        Ramp+
         |           |            | I- 80                         Ramp+
         |           |            | 14th                          St  +
         |           |            | I- 880                            +
         |           |            | Mac Arthur                    Blvd+
         |           |            | Mission                       Blvd+
...
 name    | t         |    -0.5125 | I- 580                        Ramp+
         |           |            | I- 880                        Ramp+
         |           |            | I- 580                            +
         |           |            | I- 680                        Ramp+
         |           |            | I- 80                         Ramp+
         |           |            | Sp Railroad                       +
         |           |            | I- 880                            +
         |           |            | State Hwy 13                  Ramp+
         |           |            | I- 80                             +
         |           |            | State Hwy 24                  Ramp+
...
 thepath | f         |          0 |
 thepath | t         |          0 |
(4 rows)
    Notez que deux lignes sont affichées pour la même colonne, une
    correspondant à la hiérarchie d'héritage complète commençant à la table
    road
    (inherited=t), et une autre
    incluant seulement la table road elle-même
    (inherited=f).
    (Par soucis de concision, nous avons seulement affiché les dix premières
    valeurs les plus communes pour la colonne name.)
   
    Les informations stockées dans pg_statistic par
    ANALYZE, en particulier le nombre maximum d'éléments
    dans les tableaux most_common_vals et
    histogram_bounds pour chaque colonne, peuvent
    être définies colonne par colonne en utilisant la commande ALTER
    TABLE SET STATISTICS ou globalement en initialisant la variable
    de configuration default_statistics_target. La
    limite par défaut est actuellement de 100 entrées. Augmenter la limite
    pourrait résulter en des estimations plus précises du planificateur, en
    particulier pour les colonnes ayant des distributions de données
    irrégulières, au prix d'un plus grand espace consommé dans
    pg_statistic et d'un temps plus long pour
    calculer les estimations. En revanche, une limite plus basse pourrait
    être suffisante pour des colonnes avec des distributions de données
    simples.
   
Le Chapitre 69 donne plus de détails sur l'utilisation des statistiques par le planificateur.
Il est habituel de voir des requêtes lentes tourner avec de mauvais plans d'exécution, car plusieurs colonnes utilisées dans les clauses de la requête sont corrélées. L'optimiseur part normalement du principe que toutes les conditions sont indépendantes les unes des autres, ce qui est faux quand les valeurs des colonnes sont corrélées. Les statistiques classiques, du fait qu'il s'agit par nature de statistiques sur une seule colonne, ne peuvent pas capturer d'information sur la corrélation entre colonnes. Toutefois, PostgreSQL a la possibilité de calculer des statistiques multivariées, qui peuvent capturer une telle information.
Comme le nombre de combinaisons de colonnes est très important, il n'est pas possible de calculer les statistiques multivariées automatiquement. À la place, des objets statistiques étendus, plus souvent appelés simplement objets statistiques, peuvent être créés pour indiquer au serveur qu'il faut obtenir des statistiques sur un ensemble intéressant de colonnes.
    Les objets statistiques sont créés en utilisant la commande CREATE
    STATISTICS. La création de tels objets crée seulement
    une entrée dans le catalogue pour exprimer l'intérêt dans cette
    statistique. La vraie récupération de données est effectuée par
    ANALYZE (soit une commande manuelle, soit une analyse
    automatique en tâche de fond). Les valeurs collectées peuvent être
    examinées dans le catalogue pg_statistic_ext_data.
   
    ANALYZE calcule des statistiques étendues basées sur le
    même ensemble de lignes de la table qu'il utilise pour calculer les
    statistiques standard sur une seule colonne. Puisque la taille
    d'échantillon peut être augmentée en augmentant la cible de statistiques
    de la table ou de n'importe laquelle de ses colonnes (comme décrit dans
    la section précédente), une plus grande cible de statistiques donnera
    normalement des statistiques étendues plus précises, mais nécessitera
    également plus de temps pour les calculer.
   
La section suivante décrit les types de statistiques étendues qui sont actuellement supportées.
     Le type le plus simple de statistiques étendues trace les
     dépendances fonctionnelles , un concept utilisé
     dans les définitions des formes normales des bases de données. On dit
     qu'une colonne b est fonctionnellement
     dépendante d'une colonne a si la connaissance
     de la valeur de a est suffisante pour
     déterminer la valeur de b, et donc qu'il
     n'existe pas deux lignes ayant la même valeur de
     a avec des valeurs différentes de
     b. Dans une base de données complètement
     normalisée, les dépendances fonctionnelles ne devraient exister que sur
     la clé primaire et les superclés. Toutefois, dans la pratique, beaucoup
     d'ensembles de données ne sont pas totalement normalisés pour de
     nombreuses raisons ; une dénormalisation intentionnelle pour des
     raisons de performances est un exemple courant. Même dans une base de
     données totalement normalisée, il peut y avoir une corrélation partielle
     entre des colonnes, qui peuvent être exprimées comme une dépendance
     fonctionnelle partielle.
    
L'existence de dépendances fonctionnelles a un impact direct sur la précision de l'estimation pour certaines requêtes. Si une requête contient des conditions à la fois sur des colonnes indépendantes et sur des colonnes dépendantes, les conditions sur les colonnes dépendantes ne réduisent plus la taille du résultat ; mais sans la connaissance de cette dépendance fonctionnelle, l'optimiseur de requêtes supposera que les conditions sont indépendantes, avec pour résultat une taille de résultat sous-estimée.
     Pour informer l'optimiseur des dépendances fonctionnelles,
     ANALYZE peut collecter des mesures sur des
     dépendances entre colonnes. Évaluer le degré de dépendance entre tous
     les ensembles de colonnes aurait un coût prohibitif, c'est pourquoi la
     collecte de données est limitée aux groupes de colonnes apparaissant
     ensemble dans un objet statistique défini avec l'option
     dependencies. Il est conseillé de ne créer des
     dépendences statistiques que pour des groupes de
     colonnes fortement corrélées, pour éviter un surcoût à la fois dans
     ANALYZE et plus tard lors de la planification de
     requête.
    
Voici un exemple de collecte de statistiques fonctionnellement dépendantes :
CREATE STATISTICS stts (dependencies) ON city, zip FROM zipcodes;
ANALYZE zipcodes;
SELECT stxname, stxkeys, stxddependencies
  FROM pg_statistic_ext join pg_statistic_ext_data on (oid = stxoid)
  WHERE stxname = 'stts';
 stxname | stxkeys |             stxddependencies
---------+---------+------------------------------------------
 stts    | 1 5     | {"1 => 5": 1.000000, "5 => 1": 0.423130}
(1 row)On peut voir ici que la colonne 1 (zip code) détermine complètement la colonne 5 (city) et que donc le coefficient est 1.0, alors que la ville ne détermine le code postal qu'environ 42% du temps, ce qui veut dire qu'il y a beaucoup de villes (58%) qui sont représentées par plus d'un seul code postal.
Lors du calcul de la sélectivité d'une requête impliquant des colonnes fonctionnellement dépendantes, le planificateur ajoute l'estimation de sélectivité par condition en utilisant les cœfficients de dépendance afin de ne pas produire de résultats sous-estimés.
      Les dépendances fonctionnelles sont pour le moment uniquement appliquées
      pour les conditions sur une simple égalité entre une colonne et une
      valeur constante et des clauses IN contenant des
      valeurs constantes. Elles ne sont pas utilisées pour améliorer
      l'estimation sur les conditions d'égalité entre deux colonnes ou la
      comparaison d'une colonne avec une expression ni pour les clauses
      d'intervalle, LIKE ou tout autre type de condition.
     
Lors d'une estimation avec des dépendances fonctionnelles, l'optimiseur part du principe que les conditions sur les colonnes impliquées sont compatibles et donc redondantes. Si elles sont incompatibles, l'estimation correcte devrait être zéro ligne, mais cette possibilité n'est pas envisagée. Par exemple, dans une requête telle que
SELECT * FROM zipcodes WHERE city = 'San Francisco' AND zip = '94105';
      l'optimiseur négligera la clause city
      puisqu'elle ne changera pas la sélectivité, ce qui est correct. Par
      contre, il fera la même supposition pour
      
SELECT * FROM zipcodes WHERE city = 'San Francisco' AND zip = '90210';
bien qu'il n'y ait en réalité aucune ligne satisfaisant cette requête. Toutefois, les statistiques de dépendances fonctionnelles ne fournissent pas suffisamment d'information pour en arriver à cette conclusion.
Pour beaucoup de situations pratiques, cette supposition est généralement correcte ; par exemple, l'application pourrait contenir une interface graphique qui n'autorise que la sélection de villes et codes postaux compatibles pour l'utilisation dans une requête. Mais si ce n'est pas le cas, les dépendances fonctionnelles pourraient ne pas être une solution viable.
     Les statistiques sur une seule colonne stockent le nombre de valeurs
     distinctes pour chaque colonne. Les estimations du nombre de valeurs
     distinctes combinant plus d'une colonne (par exemple, pour
     GROUP BY a, b) sont souvent fausses quand
     l'optimiseur ne dispose que de données statistiques par colonne, avec
     pour conséquence le choix de mauvais plans.
    
     Afin d'améliorer de telles estimations, ANALYZE peut
     collecter des statistiques n-distinct pour des groupes de colonne. Comme
     précédemment, il n'est pas envisageable de le faire pour tous les
     regroupements possibles, ainsi les données ne sont collectées que pour
     ceux apparaissant ensemble dans un objet statistique défini avec
     l'option ndistinct. Des données seront collectées
     pour chaque combinaison possible de deux colonnes ou plus dans
     l'ensemble de colonnes listées.
    
En continuant avec l'exemple précédent, le nombre n-distinct dans une table de code postaux pourrait ressembler à ceci :
CREATE STATISTICS stts2 (ndistinct) ON city, state, zip FROM zipcodes;
ANALYZE zipcodes;
SELECT stxkeys AS k, stxdndistinct AS nd
  FROM pg_statistic_ext join pg_statistic_ext_data on (oid = stxoid)
  WHERE stxname = 'stts2';
-[ RECORD 1 ]--------------------------------------------------------
k  | 1 2 5
nd | {"1, 2": 33178, "1, 5": 33178, "2, 5": 27435, "1, 2, 5": 33178}
(1 row)Cela indique qu'il y a trois combinaisons de colonnes qui ont 33178 valeurs distinctes : le code postal et l'état ; le code postal et la ville ; et le code postal, la ville et l'état (le fait qu'ils soient tous égaux est attendu puisque le code postal seul est unique dans cette table). D'un autre côté, la combinaison de la ville et de l'état n'a que 27435 valeurs distinctes.
     Il est conseillé de créer des objets statistiques
     ndistinct uniquement sur les combinaisons de colonnes
     réellement utilisées pour des regroupements, et pour lesquelles les
     mauvaises estimations du nombre de groupe a pour conséquence de mauvais
     plans. Sinon le temps consommé par ANALYZE serait
     gaspillé.
    
Un autre type de statistiques enregistrées pour chaque colonne est les listes des valeurs les plus communes. Ceci permet des estimations très précises pour les colonnes individuelles, mais pourrait résulter en des estimations significativement mauvaises pour les requêtes ayant des filtres sur plusieurs colonnes.
     Pour améliorer ces estimations, ANALYZE peut récupérer
     des listes MCV sur des combinaisons de colonnes. De façon similaire aux
     dépendances fonctionnelles et coefficients de valeurs distinctes, il
     n'est pas possible de le faire pour chaque regroupement de colonnes.
     Ceci est encore plus vrai dans ce cas, car la liste MCV
     (contrairement aux dépendances fonctionnelles et coefficients de valeurs
     distinctes), enregistre les valeurs les plus communes. Donc les données
     ne sont récupérées que pour les groupes de colonnes apparaissant dans un
     objet statistique défini avec l'option mcv.
    
En continuant sur l'exemple précédent, la liste MCV pour une table de codes ZIP pourrait ressembler à ce qui suit (contrairement aux types plus simples de statistiques, une fonction est requise pour inspecter le contenu du MCV) :
CREATE STATISTICS stts3 (mcv) ON city, state FROM zipcodes;
ANALYZE zipcodes;
SELECT m.* FROM pg_statistic_ext join pg_statistic_ext_data on (oid = stxoid),
                pg_mcv_list_items(stxdmcv) m WHERE stxname = 'stts3';
 index |         values         | nulls | frequency | base_frequency
-------+------------------------+-------+-----------+----------------
     0 | {Washington, DC}       | {f,f} |  0.003467 |        2.7e-05
     1 | {Apo, AE}              | {f,f} |  0.003067 |        1.9e-05
     2 | {Houston, TX}          | {f,f} |  0.002167 |       0.000133
     3 | {El Paso, TX}          | {f,f} |     0.002 |       0.000113
     4 | {New York, NY}         | {f,f} |  0.001967 |       0.000114
     5 | {Atlanta, GA}          | {f,f} |  0.001633 |        3.3e-05
     6 | {Sacramento, CA}       | {f,f} |  0.001433 |        7.8e-05
     7 | {Miami, FL}            | {f,f} |    0.0014 |          6e-05
     8 | {Dallas, TX}           | {f,f} |  0.001367 |        8.8e-05
     9 | {Chicago, IL}          | {f,f} |  0.001333 |        5.1e-05
   ...
(99 rows)Ceci indique que la combinaison la plus commune des colonnes city et state est Washington DC, avec la fréquence réelle (dans cet exemple) de 0,35 %. La fréquence de base de la combinaison (telle qu'elle est calculée par les fréquences par mono colonne) est seulement de 0,0027 %, résultant en une sous-estimation très forte.
     Il est préférable de créer des objets statistiques MCV
     uniquement sur les combinaisons de colonnes réellement utilisées
     ensemble dans des filtres et pour lesquelles la mauvaise estimation du
     nombre de groupes a pour conséquence de mauvais plans. Dans le cas
     contraire, le ANALYZE et le temps de planification
     sont juste gâchés.