databases25 min

Query Planning

How databases optimize and execute your queries

0/9Not Started

Why This Matters

You write a SQL query, but you do not decide HOW the database executes it. That job belongs to the query planner -- an optimizer built into every database engine. It analyzes your query, considers available indexes, estimates costs, and picks the fastest execution strategy. The same query can be executed in dozens of different ways, and the planner chooses the best one.

Understanding the query planner is what separates developers who write "working" queries from those who write fast queries. When your API endpoint takes 5 seconds because of a slow query, running EXPLAIN and reading the execution plan is how you diagnose and fix it. This is a skill every backend engineer needs.

Define Terms

Visual Model

Resultfinal output
SortORDER BY name
Hash Joinusers.id = orders.user_id
Seq Scanusers (100 rows)
Index Scanorders (idx_user_id)
Filtertotal > 50

The full process at a glance. Click Start tour to walk through each step.

The query planner builds an execution tree: scans at leaves, joins in the middle, sort and result at top.

Code Example

Code
-- EXPLAIN: see the query plan WITHOUT executing
EXPLAIN
SELECT * FROM users WHERE email = alice@test.com;
-- Without index: Seq Scan on users (cost=0.00..25.00 rows=1)
-- With index:    Index Scan using idx_users_email (cost=0.00..8.00 rows=1)

-- EXPLAIN ANALYZE: execute and show actual timing
EXPLAIN ANALYZE
SELECT users.name, COUNT(orders.id) AS order_count
FROM users
JOIN orders ON users.id = orders.user_id
GROUP BY users.name;
-- Hash Join (cost=... rows=100) (actual time=0.5..1.2 rows=100)
--   -> Seq Scan on users (actual time=0.01..0.1 rows=100)
--   -> Hash (actual time=0.3..0.3 rows=5000)
--     -> Seq Scan on orders (actual time=0.01..0.2 rows=5000)

-- Join strategies the planner considers:

-- 1. Nested Loop Join: for each row in table A,
--    scan all rows in table B. O(n*m). Good for small tables.

-- 2. Hash Join: build a hash table from the smaller table,
--    then probe it for each row of the larger table. O(n+m).

-- 3. Merge Join: sort both tables, then merge. O(n log n + m log m).
--    Great when both sides are already sorted (via index).

-- The N+1 problem:
-- BAD: 1 query for users + N queries for each users orders
SELECT * FROM users;  -- 1 query
-- Then for EACH user:
SELECT * FROM orders WHERE user_id = 1;  -- N queries
SELECT * FROM orders WHERE user_id = 2;
-- ...

-- GOOD: 1 query with a JOIN
SELECT users.name, orders.total
FROM users
JOIN orders ON users.id = orders.user_id;  -- 1 query

Interactive Experiment

Try these exercises:

  • Run EXPLAIN on a simple SELECT query. Then add an index on the WHERE column and run EXPLAIN again. Compare the plans.
  • Write a query that JOINs three tables. Run EXPLAIN ANALYZE and identify which join strategy the planner chose (nested loop, hash, or merge).
  • Demonstrate the N+1 problem: query a list of items, then loop and query related items individually. Count the total number of queries.
  • Rewrite the N+1 example as a single JOIN query. Compare the performance.

Quick Quiz

Coding Challenge

Detect N+1 Queries

Write a function called `detectNPlusOne` that takes an array of query log strings and identifies N+1 patterns. An N+1 pattern occurs when there is one query like 'SELECT * FROM users' followed by multiple queries like 'SELECT * FROM orders WHERE user_id = X' with different values of X. Return an object with `detected` (boolean) and `count` (number of repeated queries).

Loading editor...

Real-World Usage

Query planning knowledge is essential for performance engineering:

  • Slow query logs: Production databases log queries above a time threshold. Reading execution plans for these queries is the first step in optimization.
  • ORM tuning: ORMs like SQLAlchemy, Django ORM, and Prisma can generate inefficient queries. Understanding the plan helps you know when to use raw SQL.
  • Database dashboards: Tools like pgAdmin, DataGrip, and EXPLAIN visualizers show graphical execution plans for debugging.
  • N+1 detection: Libraries like Bullet (Rails) and nplusone (Python) automatically detect N+1 queries in development.
  • Cost-based decisions: Should you add an index? Run EXPLAIN before and after to see if the planner actually uses it.

Connections