🔝 Retour au Sommaire
Imaginez que vous devez organiser un voyage en voiture :
- Sans carte ni GPS : Vous roulez au hasard, vous vous perdez, vous perdez du temps
- Avec une carte à jour : Vous connaissez les routes, les distances, les limitations, vous choisissez le meilleur itinéraire
PostgreSQL fonctionne de la même manière pour exécuter vos requêtes SQL !
Pour chaque requête, PostgreSQL doit décider :
- Quel index utiliser (ou ne pas utiliser) ?
- Dans quel ordre joindre les tables ?
- Quelle stratégie de scan adopter ?
Pour prendre ces décisions intelligemment, PostgreSQL a besoin de statistiques à jour sur vos données. C'est exactement le rôle d'ANALYZE.
ANALYZE est une commande qui :
- Collecte des statistiques sur le contenu de vos tables
- Stocke ces statistiques dans les tables système (
pg_statistics) - Permet au planificateur de choisir le meilleur plan d'exécution pour vos requêtes
ANALYZE; -- Analyse toute la base de données
ANALYZE nom_table; -- Analyse une table spécifique
ANALYZE nom_table (col1, col2); -- Analyse des colonnes spécifiques | VACUUM | ANALYZE |
|---|---|
| Nettoie les lignes mortes | Collecte des statistiques |
| Récupère l'espace disque | N'affecte pas l'espace disque |
| Prévient le XID wraparound | Optimise les plans de requêtes |
| Impact : Santé de la base | Impact : Performance des requêtes |
Bonne pratique : Souvent utilisés ensemble avec VACUUM ANALYZE.
Le Query Planner (ou planificateur de requêtes) est le composant de PostgreSQL qui :
- Reçoit votre requête SQL
- Génère plusieurs plans d'exécution possibles
- Estime le coût de chaque plan
- Choisit le plan le moins coûteux
Prenons cette requête :
SELECT * FROM clients WHERE ville = 'Paris';Le planificateur a plusieurs options :
Option A - Sequential Scan :
- Parcourir toute la table ligne par ligne
- Coût estimé : 10 000 unités
Option B - Index Scan :
- Utiliser un index sur la colonne
ville - Coût estimé : 150 unités
Décision : Le planificateur choisit l'index (Option B) car c'est plus rapide !
Le planificateur utilise des statistiques pour évaluer :
- Nombre de lignes dans la table (
n_live_tup) - Nombre de lignes qui correspondent au filtre (sélectivité)
- Distribution des valeurs (valeurs les plus fréquentes, histogrammes)
- Taille des données (pages, tuples)
- Corrélation entre l'ordre physique et l'ordre logique
Sans statistiques à jour, le planificateur est aveugle !
Pour chaque table :
-- Voir les statistiques d'une table
SELECT
schemaname,
tablename,
n_live_tup, -- Nombre de lignes vivantes
n_dead_tup, -- Nombre de lignes mortes
last_analyze, -- Dernière analyse manuelle
last_autoanalyze -- Dernière analyse automatique
FROM pg_stat_user_tables
WHERE tablename = 'clients'; Pour chaque colonne, PostgreSQL collecte :
- null_frac : Proportion de valeurs NULL
- avg_width : Largeur moyenne des valeurs (en octets)
- n_distinct : Nombre de valeurs distinctes (cardinalité)
- most_common_vals : Les valeurs les plus fréquentes (MCV)
- most_common_freqs : Fréquences de ces valeurs
- histogram_bounds : Distribution des valeurs (pour les autres valeurs)
- correlation : Corrélation entre ordre physique et logique
-- Voir les statistiques de la colonne "ville"
SELECT
attname,
n_distinct,
most_common_vals,
most_common_freqs
FROM pg_stats
WHERE tablename = 'clients'
AND attname = 'ville';Résultat possible :
attname | n_distinct | most_common_vals | most_common_freqs
--------|------------|-------------------------------|------------------
ville | 50 | {Paris,Lyon,Marseille,Lille} | {0.35,0.15,0.12,0.08}
Interprétation :
- Il y a 50 villes distinctes
- 35% des clients sont à Paris
- 15% à Lyon, 12% à Marseille, 8% à Lille
Le planificateur utilise ces informations pour estimer combien de lignes retournera WHERE ville = 'Paris' → environ 35% !
ANALYZE ne lit pas toute la table ! Il utilise un échantillonnage statistique :
- Sélection aléatoire de pages (blocs de 8 Ko)
- Lecture d'un échantillon de lignes
- Extrapolation des statistiques à toute la table
-- Voir le paramètre actuel
SHOW default_statistics_target; -- Défaut : 100Ce paramètre contrôle :
- Nombre d'échantillons collectés par ANALYZE
- Précision des statistiques (histogrammes, MCV)
- Temps d'exécution d'ANALYZE
Valeurs :
- 10 : Rapide mais imprécis
- 100 : Défaut, bon équilibre
- 1000 : Très précis mais lent (pour les colonnes critiques)
- 10000 : Maximum, pour les cas extrêmes
Si une colonne est cruciale pour vos requêtes (ex : dans les WHERE, JOIN) :
-- Augmenter la précision des statistiques pour la colonne "ville"
ALTER TABLE clients
ALTER COLUMN ville SET STATISTICS 500;
-- Puis relancer ANALYZE
ANALYZE clients (ville);Impact :
- ✅ Meilleure estimation du planificateur pour cette colonne
⚠️ ANALYZE sera un peu plus lent sur cette colonne
Exemple classique : Le planificateur choisit un Sequential Scan alors qu'un Index Scan serait plus rapide.
EXPLAIN SELECT * FROM commandes WHERE client_id = 12345;Avec statistiques obsolètes :
Seq Scan on commandes (cost=0.00..25000.00 rows=500000 width=100)
Filter: (client_id = 12345)
→ Le planificateur pense qu'il y a 500 000 lignes, alors qu'en réalité il n'y en a que 50.
Avec statistiques à jour :
Index Scan using idx_commandes_client_id on commandes
(cost=0.42..8.44 rows=50 width=100)
Index Cond: (client_id = 12345)
→ Le planificateur sait qu'il y a 50 lignes, il choisit l'index !
Vous remarquez que vos requêtes deviennent de plus en plus lentes avec le temps, sans raison apparente :
- Les données ont beaucoup évolué (insertions, suppressions)
- Les statistiques ne reflètent plus la réalité
- Le planificateur fait de mauvais choix
Solution : ANALYZE !
Dans l'output d'EXPLAIN ANALYZE, vous voyez :
→ Index Scan (cost=... rows=10 ...) (actual rows=100000 ...)
- rows=10 : Estimation du planificateur
- actual rows=100000 : Réalité
Ratio estimation/réalité de 1:10 000 → Statistiques obsolètes !
-
Après un chargement massif de données
-- Import de 10 millions de lignes COPY clients FROM '/data/clients.csv' WITH CSV; -- ANALYZE immédiatement après ANALYZE clients;
-
Après des modifications massives
-- Mise à jour de 50% de la table UPDATE produits SET prix = prix * 1.1 WHERE categorie = 'electronique'; -- ANALYZE pour mettre à jour les distributions ANALYZE produits;
-
Après une suppression importante
-- Suppression de 80% des anciennes données DELETE FROM logs WHERE created_at < NOW() - INTERVAL '90 days'; -- ANALYZE (souvent avec VACUUM) VACUUM ANALYZE logs;
-
Avant une requête critique
-- Vous allez lancer une requête complexe importante ANALYZE; -- Assure que les statistiques sont fraîches -- Puis votre requête complexe SELECT ... FROM ... WHERE ... JOIN ... ;
-
Après la création d'un index
CREATE INDEX idx_clients_email ON clients(email); -- ANALYZE pour que le planificateur connaisse l'index ANALYZE clients;
Comme pour VACUUM, PostgreSQL dispose d'auto-ANALYZE qui lance automatiquement ANALYZE quand c'est nécessaire.
Formule de déclenchement :
Seuil = autovacuum_analyze_threshold + (autovacuum_analyze_scale_factor × nombre_total_de_lignes)
Paramètres par défaut :
SHOW autovacuum_analyze_threshold; -- 50 lignes modifiées
SHOW autovacuum_analyze_scale_factor; -- 0.1 (10% de la table) Exemple :
- Table avec 100 000 lignes
- Seuil = 50 + (0.1 × 100 000) = 10 050 modifications
- Auto-ANALYZE se déclenche après 10 050 INSERT/UPDATE/DELETE
-- Voir les dernières auto-analyses
SELECT
schemaname,
tablename,
last_analyze,
last_autoanalyze,
analyze_count,
autoanalyze_count,
n_mod_since_analyze -- Nombre de modifications depuis le dernier ANALYZE
FROM pg_stat_user_tables
WHERE schemaname = 'public'
ORDER BY n_mod_since_analyze DESC; Indicateurs :
n_mod_since_analyze > 10 000→ ANALYZE devrait se déclencher bientôtlast_autoanalyzetrès ancien avec beaucoup de modifications → Problème potentiel
PostgreSQL 18 enrichit la vue pg_stat_all_tables avec de nouvelles métriques :
-- Nouvelles colonnes dans PG 18
SELECT
schemaname,
tablename,
last_vacuum,
last_autovacuum,
last_analyze,
last_autoanalyze,
vacuum_count,
autovacuum_count,
analyze_count,
autoanalyze_count,
n_ins_since_vacuum, -- Nouveau PG 18
n_del_since_vacuum, -- Nouveau PG 18
n_mod_since_analyze -- Amélioré PG 18
FROM pg_stat_all_tables
WHERE schemaname = 'public'; Avantages :
- Meilleure visibilité sur l'activité de maintenance
- Détection plus facile des tables nécessitant ANALYZE
- Monitoring plus fin
Avec les améliorations de PG 18 sur autovacuum, auto-ANALYZE bénéficie aussi de :
- Meilleure allocation des workers
- Ajustements dynamiques selon la charge
- Réactivité accrue sur les tables à forte activité
| Commande | Quand l'utiliser |
|---|---|
ANALYZE |
Vous voulez uniquement mettre à jour les statistiques (rapide) |
VACUUM ANALYZE |
Vous voulez nettoyer ET mettre à jour les statistiques (plus lent) |
VACUUM |
Vous voulez uniquement nettoyer (sans mise à jour des stats) |
ANALYZE est très rapide comparé à VACUUM :
- ANALYZE : Échantillonnage, ne lit pas toute la table → quelques secondes même sur de grosses tables
- VACUUM : Parcourt toute la table pour nettoyer → plusieurs minutes à heures
Exemple :
-- Sur une table de 100 millions de lignes
ANALYZE clients; -- ~5 secondes
VACUUM clients; -- ~10 minutes
VACUUM ANALYZE clients; -- ~10 minutes (VACUUM + quelques secondes pour ANALYZE) -- Tables avec beaucoup de modifications depuis le dernier ANALYZE
SELECT
schemaname || '.' || tablename AS table_name,
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS size,
n_live_tup,
n_mod_since_analyze,
ROUND(n_mod_since_analyze * 100.0 / NULLIF(n_live_tup, 0), 2) AS pct_modified,
last_analyze,
last_autoanalyze
FROM pg_stat_user_tables
WHERE n_mod_since_analyze > 1000
ORDER BY n_mod_since_analyze DESC
LIMIT 20; Indicateurs :
pct_modified > 10%→ ANALYZE recommandépct_modified > 20%→ ANALYZE urgent
-- Colonnes avec statistiques par défaut (peut-être insuffisantes)
SELECT
schemaname || '.' || tablename AS table_name,
attname AS column_name,
n_distinct,
null_frac
FROM pg_stats
WHERE schemaname = 'public'
AND n_distinct BETWEEN -1 AND 100 -- Peu de valeurs distinctes détectées
ORDER BY tablename, attname;Utilisez EXPLAIN ANALYZE et comparez rows (estimation) vs actual rows :
EXPLAIN ANALYZE
SELECT * FROM commandes WHERE statut = 'livree'; Output :
Seq Scan on commandes (cost=0.00..1500.00 rows=5000 width=200)
(actual time=0.050..25.123 rows=95000 loops=1)
- rows=5000 (estimation) vs actual rows=95000 (réalité)
- Ratio de 1:19 → Les statistiques sont obsolètes !
Solution :
ANALYZE commandes;Contexte : Vous importez 1 million de lignes chaque nuit dans une table transactions.
-- Script d'import
BEGIN;
-- Import des données
COPY transactions FROM '/data/daily_transactions.csv' WITH CSV;
-- ANALYZE immédiatement après pour que les requêtes du matin soient optimales
ANALYZE transactions;
COMMIT;Pourquoi ? Les requêtes du matin utiliseront des statistiques fraîches et seront rapides.
Problème : Une requête qui était rapide est devenue lente.
-- Requête lente
SELECT COUNT(*) FROM ventes WHERE pays = 'France';Diagnostic :
-- 1. Vérifier les statistiques
SELECT
last_analyze,
n_mod_since_analyze,
n_live_tup
FROM pg_stat_user_tables
WHERE tablename = 'ventes';
-- Résultat : last_analyze = il y a 30 jours, n_mod_since_analyze = 500000 !
-- 2. Lancer ANALYZE
ANALYZE ventes;
-- 3. Relancer la requête → Rapide à nouveau !Contexte : Vous ajoutez une colonne et créez un index dessus.
-- Ajout d'une colonne
ALTER TABLE clients ADD COLUMN score_fidelite INT;
-- Remplissage
UPDATE clients SET score_fidelite = FLOOR(RANDOM() * 100);
-- Création d'un index
CREATE INDEX idx_clients_score ON clients(score_fidelite);
-- ANALYZE pour que le planificateur connaisse la distribution
ANALYZE clients (score_fidelite);
-- Maintenant les requêtes utilisent efficacement l'index
SELECT * FROM clients WHERE score_fidelite > 80;Contexte : Une table produits où 90% des produits sont de catégorie "standard" et 10% "premium".
-- Augmenter la précision des statistiques pour la colonne critique
ALTER TABLE produits
ALTER COLUMN categorie SET STATISTICS 1000;
-- ANALYZE avec statistiques détaillées
ANALYZE produits (categorie);
-- Vérifier les statistiques collectées
SELECT
attname,
n_distinct,
most_common_vals,
most_common_freqs
FROM pg_stats
WHERE tablename = 'produits' AND attname = 'categorie'; Résultat : Le planificateur peut maintenant différencier efficacement les requêtes sur "standard" (beaucoup de lignes) vs "premium" (peu de lignes).
Dans la plupart des cas, auto-ANALYZE suffit :
- ✅ Activé par défaut (via autovacuum)
- ✅ Se déclenche automatiquement selon l'activité
- ✅ Léger et non bloquant
- ✅ Après un import/export massif
- ✅ Après création d'index
- ✅ Avant une migration ou une opération critique
- ✅ En cas de requête lente avec mauvaises estimations
-- Après une grosse opération
VACUUM ANALYZE ma_table;Cela nettoie ET met à jour les statistiques en une seule passe.
Pour les colonnes fréquemment utilisées dans WHERE, JOIN, ORDER BY :
ALTER TABLE ma_table
ALTER COLUMN colonne_importante SET STATISTICS 500;
ANALYZE ma_table (colonne_importante);Mettez en place des alertes sur :
- Tables avec
n_mod_since_analyzeélevé - Tables avec
last_autoanalyzeancien (> 7 jours) - Requêtes avec écarts importants entre estimations et réalité
ANALYZE est léger mais pas gratuit :
- ❌ Ne pas lancer ANALYZE après chaque INSERT
- ✅ Lancer ANALYZE après des modifications significatives (> 10% de la table)
Toujours analyser après la création d'un index :
CREATE INDEX idx_nouveau ON ma_table(colonne);
ANALYZE ma_table; -- Important ! Cela permet au planificateur de connaître immédiatement le nouvel index.
Symptômes : last_autoanalyze est NULL ou très ancien.
Causes possibles :
-
Autovacuum désactivé
SHOW autovacuum; -- Vérifier que c'est 'on' -
Seuil trop élevé pour une petite table
-- Ajuster pour la table spécifique ALTER TABLE petite_table SET ( autovacuum_analyze_threshold = 10, autovacuum_analyze_scale_factor = 0.05 );
Causes :
default_statistics_targettrop élevé- Table très grande avec beaucoup de colonnes
- I/O disque saturé
Solutions :
-- Analyser seulement les colonnes importantes
ANALYZE ma_table (col1, col2, col3);
-- Réduire temporairement statistics_target
SET default_statistics_target = 50;
ANALYZE ma_table;
RESET default_statistics_target; Causes :
- Distribution de données très atypique
- Corrélation entre colonnes non capturée
- Statistics_target insuffisant
Solutions :
-- Augmenter drastiquement pour la colonne problématique
ALTER TABLE ma_table
ALTER COLUMN colonne_problematique SET STATISTICS 5000;
ANALYZE ma_table (colonne_problematique);
-- Vérifier les nouvelles statistiques
SELECT * FROM pg_stats
WHERE tablename = 'ma_table'
AND attname = 'colonne_problematique';✅ Vérifier auto-ANALYZE :
SELECT COUNT(*) FROM pg_stat_user_tables
WHERE n_mod_since_analyze > 10000;
-- Devrait être proche de 0✅ Identifier les tables "en retard" :
SELECT tablename, n_mod_since_analyze, last_autoanalyze
FROM pg_stat_user_tables
WHERE n_mod_since_analyze > 10000
ORDER BY n_mod_since_analyze DESC; ✅ ANALYZE manuel si nécessaire :
ANALYZE; -- Ou sur tables spécifiques- ✅ Après migration de données :
ANALYZE; - ✅ Après changement de schéma :
ANALYZE tables_modifiées; - ✅ Après création d'index :
ANALYZE tables_indexées;
- ✅ Audit des statistiques obsolètes
- ✅ Vérification des ratios estimation/réalité dans les requêtes lentes
- ✅ Ajustement des
statistics_targetsi nécessaire
- Collecte de statistiques : ANALYZE échantillonne vos données pour collecter des statistiques
- Optimisation du planificateur : Ces statistiques permettent au planificateur de choisir les meilleurs plans d'exécution
- Auto-ANALYZE : Processus automatique qui gère la plupart des cas
- Rapide et léger : ANALYZE est beaucoup plus rapide que VACUUM
- Complémentaire à VACUUM : Souvent utilisé ensemble (
VACUUM ANALYZE)
| Situation | Action |
|---|---|
| Import massif de données | ✅ ANALYZE immédiat |
| Création d'index | ✅ ANALYZE immédiat |
| Modifications importantes (>10% de la table) | ✅ ANALYZE manuel |
| Requêtes avec mauvaises estimations | ✅ ANALYZE puis vérifier |
| Maintenance quotidienne normale | ⚪ Auto-ANALYZE suffit |
┌─────────────────────────────────────────┐
│ VACUUM ANALYZE │
│ ───────────────────────────────────── │
│ Nettoie Collecte statistiques │
│ Récupère N'affecte pas │
│ l'espace l'espace disque │
│ Lent (minutes) Rapide (secondes) │
│ Santé DB Performance requêtes │
└─────────────────────────────────────────┘
-- Explorer toutes les statistiques d'une table
SELECT * FROM pg_stats WHERE tablename = 'clients' \gxColonnes intéressantes :
- n_distinct : Nombre de valeurs distinctes (-1 = unique, positif = estimé)
- most_common_vals : Top N valeurs les plus fréquentes
- most_common_freqs : Leurs fréquences
- histogram_bounds : Distribution des valeurs restantes
- correlation : Corrélation ordre physique/logique (important pour les index)
Pour les colonnes corrélées, PostgreSQL 10+ permet de créer des statistiques étendues :
-- Exemple : colonnes ville et code_postal sont corrélées
CREATE STATISTICS stats_ville_cp (dependencies)
ON ville, code_postal FROM clients;
ANALYZE clients;
-- Le planificateur comprend maintenant la corrélationPour des cas très spécifiques :
-- Maximum absolu
ALTER TABLE ma_table ALTER COLUMN col SET STATISTICS 10000;
-- Désactiver les statistiques (rare, pour debugging)
ALTER TABLE ma_table ALTER COLUMN col SET STATISTICS 0;ANALYZE est l'outil qui permet à PostgreSQL de prendre des décisions intelligentes :
- 🧠 Cerveau du planificateur : Fournit les données nécessaires aux décisions
- ⚡ Rapide et léger : Quelques secondes même sur de grosses tables
- 🤖 Automatisé : Auto-ANALYZE gère la plupart des cas
- 🎯 Impact direct sur les performances : Meilleures requêtes = application plus rapide
En tant que développeur ou DevOps, retenez :
- Auto-ANALYZE fait le travail, mais surveillez les tables à forte activité
- ANALYZE manuel après imports/modifications massives (> 10% de la table)
- Combinez VACUUM ANALYZE après nettoyage de données
- Ajustez statistics_target pour les colonnes critiques
- Surveillez les estimations dans vos requêtes lentes (EXPLAIN ANALYZE)
PostgreSQL 18 améliore encore le système de statistiques avec de nouvelles métriques dans pg_stat_all_tables, vous donnant une visibilité encore meilleure sur la santé de vos bases de données.
Avec VACUUM (maintenance physique) et ANALYZE (optimisation du planificateur), vous disposez des deux piliers essentiels pour maintenir PostgreSQL en pleine forme !
Prochaines étapes recommandées :
- Approfondir EXPLAIN et le planificateur (Chapitre 13.7)
- Explorer les index et leur impact sur les plans (Chapitre 13)
- Découvrir pg_stat_statements pour l'analyse des requêtes (Chapitre 14.1)
⏭️ Nouveauté PG 18 : Autovacuum et ajustements dynamiques (autovacuum_worker_slots)