A Peek at the Query Planner
Twice in this chapter you have been told "the engine finds a better route" and asked to take it on faith. This page finally asks the engine directly. The asking word is EXPLAIN: put it in front of any query, and instead of running it, the database tells you how it would run it — scan or index, in its own words.
That one word turns performance from folklore into a conversation. This is a peek, honestly labeled: real plans go far deeper, and reading them properly belongs to the PostgreSQL Deep Dive. What a beginner needs is two words of literacy and the habit of asking — both fit on this page.
SQL Says What; the Planner Picks How
First, meet the component you are about to talk to. Back in Chapter 3 we compared a query to ordering from a menu: you name the dish, not the cooking steps. That bargain is called declarative — SQL declares what you want, and the engine works out how. Somewhere between your SELECT and the disk, something has to make that "how" decision: full scan or index hop, which table to read first in a JOIN, whether sorting is even needed.
That something is the query planner — the part of the DBMS that looks at your query, looks at the tables and indexes available, and chooses among the possible routes before a single row is read. It has been on duty since your first SELECT in Chapter 3; you just couldn't see its decisions until now.
Think of naming a destination to a taxi driver. You said where; the route was always her call — she knows the roads, the traffic, the shortcuts. And crucially, you are allowed to ask her which way she plans to go. EXPLAIN is that question, put to the planner. Analogy mapped; let's go ask.
EXPLAIN, Read Gently
Take the chapter's slow query and put the one new word in front. Note what this does not do: it does not run the query. The planner describes its chosen route and stops — no rows are read, nothing changes, which makes EXPLAIN safe to point at anything:
EXPLAIN SELECT screening_id, seat, booked_at FROM bookings WHERE customer_id = 214;
Run this before Topic 35's index existed, and the answer's first line reads like this (trimmed to the line that matters):
Seq Scan on bookings (...)
Seq Scan — sequential scan — is the full scan from Topic 34, wearing its official name: read the bookings table front to back, testing every row. The engine is not hiding anything; it tells you plainly that it plans to touch all forty thousand rows. Now create the index and ask the same question again:
Index Scan using idx_bookings_customer on bookings (...)
Index Scan — and it even names which index it will use. Same query, before and after, and the entire lesson of this chapter compressed into a two-word difference. One honest caveat: the output's shape varies by engine — PostgreSQL speaks like the lines above, SQLite and MySQL phrase their plans differently — but the two words to hunt for, scan versus index, are the same conversation everywhere.
Why It Might Still Scan
Here is where the planner earns respect instead of obedience. Create an index, run EXPLAIN, and sometimes the answer is still Seq Scan. The beginner reflex is to assume the engine ignored you. Almost always, it out-thought you.
Suppose Lora asks for every booking of the past year — say ninety percent of the table. The index could find them, but think through the work: hop through the index, and for each of thirty-six thousand entries, follow a pointer back into the table. All that hopping and jumping ends up costlier than simply reading the table straight through once. When a query wants most of the table anyway, the scan genuinely is the faster route, and the planner knows it.
The same logic covers small tables: scanning the films table's few dozen rows is quicker than any detour through an index. The planner weighs each route using statistics it keeps about your tables — roughly how many rows, roughly how values are spread — and plays the odds. "The planner ignored my index" almost always decodes to "the planner correctly judged my index unhelpful for this query." That sentence prevents index-worship, the belief that more index use is always better; the goal was never to use indexes, it was to touch fewer rows.
The Boundary, Drawn Kindly
You have seen EXPLAIN output trimmed to one line, and honesty requires saying what was trimmed: real output comes with cost estimates, row predictions, and nested plans for JOINs — a whole vocabulary for reading the planner's reasoning in detail. That vocabulary is the opening act of the PostgreSQL Deep Dive, and it can wait. What you take from this page is the two-word literacy — did it scan or use the index? — and the habit of asking before assuming.
And with that, step back and look at what this book has quietly assembled. You declare the schema; the database enforces it. You declare the question; the planner picks the route. You declare an index; every matching query accelerates without being touched. At every layer you say what, and a reasoning engine handles how. The database was smart all along — this chapter just introduced you to the part of it that does the thinking.
- "EXPLAIN runs the query." It shows the plan only — the planner estimates without executing, so no rows are read and nothing changes. That makes it safe to point at anything, even the scary UPDATE.
- "Seq Scan appearing means something is wrong." For small tables and for queries that want a big fraction of the rows, the scan is the right call. The planner plays odds, not favorites.
- "The planner sometimes just ignores indexes." It costs them against the alternatives, every time. "Ignored" almost always means "correctly judged unhelpful for this particular query."
- "EXPLAIN output is the same everywhere." The format varies by engine — PostgreSQL, MySQL, and SQLite each phrase their plans differently. The scan-versus-index distinction you're hunting for is universal.
- "Did it scan or use the index?" is the diagnostic question behind most everyday slowness — and after this page you can ask it yourself, on any query, in one word.
- Meeting the planner completes the book's architecture: declared schema, declarative queries, and an engine that reasons about routes. Understanding that it chooses — and that you can ask — is the literacy; reading its full reasoning is the deep dive.
Knowledge Check
What happens when you run EXPLAIN in front of a query?
- The query runs normally, and its execution time is printed at the end
- The database shows its chosen plan without executing the query
- The database creates whatever index the query is missing
- The query is checked for syntax errors but nothing else
EXPLAIN's answer changes from "Seq Scan on bookings" to "Index Scan using idx_bookings_customer". What does that tell you?
- The query was rewritten into a more efficient form
- The route changed from reading every row to hopping through the index
- The first run read the whole table and the second run read a saved answer
- The bookings table was reorganized into sorted order
A column has an index, yet EXPLAIN still says Seq Scan. What is the most likely explanation?
- The index hasn't been activated yet and needs a first query to wake it
- The planner has a bug and completely failed to notice the index existed
- EXPLAIN is showing a stale plan from before the index existed
- The scan is genuinely cheaper for this query, and the planner knows it
Why does asking for 90% of a table make the index route slower than a scan?
- Following a pointer back per row costs more than one straight read-through
- Indexes flatly refuse to return more than a fixed maximum number of rows at once
- The index only covers a sample of the table's rows
- Large results must be sorted, and indexes cannot sort
You got correct