PostgreSQL Performance & Query Optimization · Leçon

Valider les estimations par rapport aux lignes réelles

Comparez les cardinalités planifiées et réelles dans EXPLAIN ANALYZE pour confirmer que les corrections statistiques ont bien été prises en compte.

Leçon 4 sur 413 étapes

Valider les estimations par rapport aux lignes réelles est une leçon PostgreSQL Performance & Query Optimization gratuite sur CoddyKit. Ceci est la leçon 4 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage PostgreSQL Performance & Query Optimization, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours PostgreSQL Performance & Query Optimization comprend 4 leçons au total.

Certaines parties de cette leçon n'ont pas encore été traduites et s'affichent en anglais.

Why Validate Estimates?

When you fix bad statistics with CREATE STATISTICS or by raising default_statistics_target, you need proof that the planner now estimates cardinalities correctly.

The single best tool is EXPLAIN ANALYZE. It runs the query and reports, for every plan node, both:

  • the planner's estimated row count (rows=)
  • the actual row count observed at runtime (actual rows=)

If estimate and actual are close, your statistics fix landed. If they diverge by 10x or 100x, the planner is still flying blind.

Reading the Two Numbers

Plain EXPLAIN shows only estimates. To get actuals you must execute the query with ANALYZE.

Each node line looks like this:

  • rows=120 — the estimate
  • actual ... rows=11500 — what really happened

A ~100x gap on the orders scan is exactly the kind of misestimate that leads to a nested loop where a hash join would have been far cheaper.

EXPLAIN ANALYZE
SELECT *
FROM orders
WHERE status = 'shipped'
  AND ship_country = 'DE';

The estimate / actual Ratio

The metric to watch is the estimation ratio per node:

  • ratio = actual_rows / estimated_rows

Interpretation:

  • ~1.0 — healthy, the planner sees the data correctly
  • > 10 or < 0.1 — a real misestimate worth investigating
  • > 100 — almost always the root cause of a bad plan

Always compare at the node where the filter or join actually applies, not just the top-level row count.

Always Multiply by loops

The most common reading mistake: actual rows is reported per loop, not as a total.

If a node shows actual ... rows=5 loops=2000, the true number of rows produced is 5 × 2000 = 10000.

Compare the planner's estimate against actual_rows × loops, never against the raw per-loop figure. Forgetting this makes a healthy inner-loop node look like a wild misestimate.

EXPLAIN ANALYZE
SELECT o.*, c.name
FROM customers c
JOIN orders o ON o.customer_id = c.id
WHERE c.region = 'EU';

A Healthy Plan Looks Like This

After a good statistics fix, estimate and actual should line up on the driving nodes. Read this fragment carefully:

  • Seq Scan estimate rows=11200 vs actual rows=11500 → ratio 1.03, healthy
  • The aggregate on top also matches closely

This is the outcome you are validating for: the numbers in parentheses on the left agree with the numbers after actual on the right.

-- Reading EXPLAIN ANALYZE output:
-- Seq Scan on orders
--   (cost=0.00..2310.0 rows=11200 width=64)
--   (actual time=0.02..14.3 rows=11500 loops=1)
--   Filter: (status = 'shipped')

Correlated Columns: The Classic Trap

The planner assumes columns are independent and multiplies their selectivities. When columns are correlated, the combined estimate collapses far below reality.

Example: most rows where city = 'Berlin' also have country = 'DE'. The planner multiplies the two fractions and predicts a tiny result, but the actual count is large.

This is precisely where multivariate extended statistics repair the estimate.

EXPLAIN ANALYZE
SELECT *
FROM addresses
WHERE city = 'Berlin'
  AND country = 'DE';

Create and Refresh Extended Statistics

To teach the planner about correlation, create an extended statistics object with the dependencies kind, then analyze the table so the new statistics are populated.

Critically: CREATE STATISTICS alone does nothing until ANALYZE runs. Validation is meaningless if you skip the refresh.

CREATE STATISTICS addr_city_country (dependencies)
  ON city, country
  FROM addresses;

ANALYZE addresses;

The Before/After Discipline

Validation is a comparison, so capture two snapshots:

  • Before: run EXPLAIN ANALYZE and record the estimate vs actual on the filtered node (e.g. rows=40 vs actual rows=9000)
  • After: create + analyze the statistics, then re-run the identical query

The fix landed only if the estimate moved toward the actual (e.g. now rows=8700 vs actual rows=9000). A changed plan shape is a bonus, not the proof — the estimate convergence is the proof.

Inspect What the Planner Now Knows

You can confirm extended statistics were computed without re-running the query, by reading pg_stats_ext.

If the dependency degrees are populated (close to 1.0 for strongly correlated pairs), ANALYZE did its job and the planner has the data it needs.

SELECT statistics_name,
       attnames,
       dependencies
FROM pg_stats_ext
WHERE tablename = 'addresses';

Use BUFFERS and Format for Clarity

For serious validation, add options to the command:

  • BUFFERS — shows shared block hits/reads, exposing I/O caused by a misestimated scan
  • FORMAT JSON — gives machine-readable Plan Rows and Plan Actual Rows fields you can diff programmatically
  • SETTINGS — echoes non-default planner settings that may be skewing the test

JSON output is ideal when scripting regression checks across many queries.

EXPLAIN (ANALYZE, BUFFERS, SETTINGS, FORMAT JSON)
SELECT *
FROM addresses
WHERE city = 'Berlin'
  AND country = 'DE';

Don't Be Fooled by Rows Removed by Filter

When the estimate still looks off, check the Rows Removed by Filter line. A node can scan millions of rows yet return few, and the estimate you validate is about returned rows.

Also confirm you are validating a representative parameter value. A query that is healthy for country = 'DE' may misestimate badly for a rare value — per-value MCV skew is normal and may need a higher statistics target rather than extended statistics.

ALTER TABLE addresses
  ALTER COLUMN country SET STATISTICS 1000;

ANALYZE addresses;

Quick Check

Test your understanding of validating estimates against actual rows.

Recap

To confirm a statistics fix landed, validate estimates against actuals with discipline:

  • Run EXPLAIN ANALYZE and read rows= (estimate) against actual ... rows= per plan node.
  • Compute the ratio; treat >10x or <0.1x as a real misestimate, >100x as a likely root cause.
  • Always multiply actual rows by loops to get the true total.
  • For correlated columns, create extended statistics (dependencies/ndistinct/mcv) and then ANALYZE — creation alone does nothing.
  • Use a before/after snapshot: the proof is the estimate converging toward the actual, not merely a changed plan.
  • Reach for BUFFERS, FORMAT JSON, and pg_stats_ext to make validation rigorous and scriptable.
Gratuit pour commencer

Apprends SQL avec un tuteur IA — gratuit

Écris et exécute du vrai code dans ton navigateur, obtiens de l'aide instantanée d'un tuteur IA disponible 24h/24, et reprends là où tu t'es arrêté sur le web ou dans l'app.

Cours
22
Leçons
88

Questions Fréquemment Posées

La leçon « Valider les estimations par rapport aux lignes réelles » est-elle gratuite ?

Oui — le texte complet de « Valider les estimations par rapport aux lignes réelles » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours PostgreSQL Performance & Query Optimization, passe à CoddyKit PRO. Le cours PostgreSQL Performance & Query Optimization comprend 4 leçons au total.

Qu'est-ce que j'apprendrai dans « Valider les estimations par rapport aux lignes réelles » ?

Comparez les cardinalités planifiées et réelles dans EXPLAIN ANALYZE pour confirmer que les corrections statistiques ont bien été prises en compte. Tu pratiques PostgreSQL Performance & Query Optimization avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.

Dois-je avoir de l'expérience pour commencer PostgreSQL Performance & Query Optimization ?

Aucune expérience préalable n'est requise. PostgreSQL Performance & Query Optimization sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 4 sur 4.

Combien de temps prend la leçon « Valider les estimations par rapport aux lignes réelles » ?

La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.

Peux-tu écrire et exécuter du code dans cette leçon PostgreSQL Performance & Query Optimization ?

Oui. Chaque leçon PostgreSQL Performance & Query Optimization inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.

Toutes les leçons de ce cours

  1. Comment le planificateur estime le nombre de lignes
  2. Statistiques multivariées pour les colonnes corrélées
  3. Corrections MCV et N-Distinct
  4. Valider les estimations par rapport aux lignes réelles
← Retour à PostgreSQL Performance & Query Optimization