open atlas
↑ Back to track
SQL & PostgreSQL, deep SQL · 00 · 02

How Postgres executes a query

A query passes through five stages — parse, analyze, rewrite, plan, execute — before a single row comes back. Knowing the pipeline tells you where time goes and which problems live at which stage.

SQL Junior ◷ 14 min
Level
FoundationsJuniorMiddleSenior

You type SELECT * FROM orders WHERE id = 42; and a row comes back in a millisecond. It feels atomic — like the database just looked it up. It isn’t atomic at all: your text went through five distinct stages, and each one is where a different class of bug or slowness lives. When a query is slow, the first senior question is always “slow at which stage?”

The five stages

Your SQL string is not run directly. The Postgres backend that owns your connection pushes it through a pipeline:

  1. Parse — the raw text is checked for syntax and turned into a parse tree. A missing comma or SELCT dies here. No tables are touched yet; the parser doesn’t even know if orders exists.
  2. Analyze (semantic) — names are resolved against the catalog. Does orders exist? Does it have a column id? What types are involved? A “column does not exist” error comes from here, not the parser.
  3. Rewrite — rule-based transformations apply. The big one: a query against a view is rewritten to hit the underlying tables; row-level security policies and rules are injected here too.
  4. Plan / optimize — the planner takes the analyzed query tree and produces a physical plan: which scan (sequential? index?), which join algorithm (nested loop? hash? merge?), in which order. It uses table statistics to estimate row counts and picks the cheapest plan by an internal cost model.
  5. Execute — the executor walks the chosen plan tree, pulling rows through it node by node, and streams the result back to you.

Together these five stages mean that by the time a row reaches you, it has been validated, resolved, transformed, optimized, and fetched — and each of those steps can fail independently. Skipping any one of them in your mental model leaves you guessing when something goes wrong.

Why the stages matter for debugging

When a query misbehaves, most engineers rewrite it first — and often waste an hour changing text that was never the problem. The pipeline gives you a faster path: match the symptom to the stage, fix at that stage, done.

The pipeline is a diagnostic map. Match the symptom to the stage:

  • Syntax error → parse stage. Pure text problem; the schema is irrelevant.
  • “relation/column does not exist” → analyze stage. The text is valid SQL but doesn’t match the catalog.
  • Query suddenly slow after a data load → plan stage. The planner’s row estimates went stale, so it picked a bad plan. The fix is usually ANALYZE to refresh statistics — not rewriting the query.
  • Query always slow, plan looks right → execute stage. The plan is fine but it’s genuinely reading too many pages; the fix is an index or less data, which changes what plans are available.

This is why the senior reflex is EXPLAIN. EXPLAIN <query> shows you the plan the planner chose without running it; EXPLAIN ANALYZE <query> runs it and shows estimated vs actual rows at each node. The gap between estimate and actual is the single most useful number in Postgres performance work — it tells you whether the planner was lied to by stale statistics.

-- See the plan only (fast, no execution):
EXPLAIN SELECT * FROM orders WHERE id = 42;

-- Run it and compare estimate vs reality:
EXPLAIN ANALYZE SELECT * FROM orders WHERE id = 42;
↻ latency

You won’t fully read a plan yet — that’s an entire unit later (08-internals-and-tuning) and the databases track covers the planner and execution-plan internals in depth. For now, internalize only the shape: text in, five stages, rows out, and EXPLAIN is your window into stage four.

Why this works

Why separate planning from execution at all? Because planning is expensive — for a query joining six tables, the number of possible join orders explodes, and the planner does real combinatorial work to find a cheap one. Postgres pays that cost once and (with prepared statements) can reuse the plan across many executions. It also means the same parsed query can be re-planned later under better statistics. Bundling plan and execute together would forfeit both wins. The databases track’s execution-plans chapter goes deep on the cost model that drives stage four.

Quiz

You run a query and get 'column "emial" does not exist'. Which stage produced this error?

Quiz

A query was fast yesterday; today, after a big bulk INSERT, it's 50× slower with the same text. Best first move?

Complete the analogy

Fill in the blank: EXPLAIN shows you the planner's chosen plan _______ running the query, while EXPLAIN ANALYZE runs it and reports actual timings and row counts.

Recall before you leave
  1. 01
    List the five stages a query passes through, in order, with one word on what each does.
  2. 02
    A query is suddenly slow after a bulk load even though its text is unchanged. Which stage is the culprit and what's the fix?
  3. 03
    What is the difference between EXPLAIN and EXPLAIN ANALYZE, and why does the estimate-vs-actual gap matter?
Recap

A query is not looked up atomically — it crosses a five-stage pipeline: parse (syntax to tree), analyze (resolve names against the catalog), rewrite (expand views, inject row-level security), plan (the cost-based optimizer turns the query tree into a physical plan of scans and joins), and execute (the executor walks that plan and streams rows). The value of knowing the pipeline is diagnostic: a syntax error is parse, a missing-column error is analyze, a sudden post-load slowdown is the plan stage starved of fresh statistics (fix with ANALYZE), and a chronically slow-but-correct plan is the execute stage doing too much I/O (fix with an index). EXPLAIN shows the chosen plan; EXPLAIN ANALYZE runs it and exposes the estimate-vs-actual gap. Now when you see a sudden regression after a bulk load, you won’t rewrite SQL — you’ll reach for ANALYZE, check the estimate-vs-actual gap in EXPLAIN ANALYZE, and fix at the right stage.

Practice

Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.

recallapplystretch0 of 4 done
Connected lessons

Something unclear?

Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.

shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.