How the Planner Chooses
Understand why PostgreSQL picks one plan over another — join strategies, stale statistics, sort spills, and how LIMIT rewrites the whole approach — on a real multi-table stand.
Reading a plan tells you what Postgres did. This course explains why it did that. We build a stand with four tables — users, orders, products, and line_items — and use it to provoke every major planning decision the planner makes. We watch a Hash Join flip to a Nested Loop the moment an index appears. We insert rows without running ANALYZE and watch the estimate diverge from reality by a factor of two hundred thousand, then fix it. We shrink work_mem until a sort spills to disk and time the difference when it stops spilling. We add a LIMIT to a query and see the planner abandon its sort entirely in favour of a backward index scan. Every effect is visible in the plan output; the course teaches you to connect what you read to the decision the planner made.
What you'll build
- Explain why Nested Loop, Hash Join, and Merge Join each suit a different input size
- Show how a new index changes a join strategy without changing the query
- Reproduce stale-statistics misestimation and fix it with ANALYZE
- Demonstrate sort spill with a small work_mem and measure the improvement when memory is sufficient
- Explain why LIMIT can eliminate a Sort node entirely when a matching index exists
- Use enable_hashjoin / enable_nestloop as a diagnostic lens, not a production fix
Contents
- A database to reason about plans on
- Wait for it to be ready
- One command to bring it up
- Start the server
- A clean slate when you need one
- A short way into the database
- The users table
- The orders table
- The products table
- The line_items table
- Create the tables
- Seed the users, repeatably
- Seed the products
- Seed a million orders
- Seed three million line items
- Load the data
- Fresh statistics, single-threaded plans
- Confirm what we are sitting on
- Nested Loop: a few outer rows, one lookup each
- Reading a Nested Loop plan
- Hash Join: one big side, one small side
- Reading a Hash Join plan
- Add an index — same query, new plan
- The plan flips to Nested Loop
- Merge Join as a diagnostic tool
- Reading a Merge Join plan
- What happens when work_mem runs out
- Sort spill in the plan
- The same sort with enough memory
- Quicksort in memory
- Insert rows the planner does not know about yet
- A query the planner cannot estimate correctly
- The estimate is off by two hundred thousand
- Refresh the statistics
- The estimate matches reality now
- Sorting the whole table
- A full sort plan
- Ask for only the top ten
- LIMIT rewrites the cost
- Index a column we sort by
- LIMIT + sorted index = no sort node
- Index Scan Backward — no sort anywhere
- Connecting the dots