ABHIJAT
← Back to Writing

Databases

Reading a query plan without panicking

EXPLAIN ANALYZE output looks intimidating until you know the four or five things it's actually trying to tell you.

Abhijat2026-076 min read5 views

Last updated September 17, 2026

EXPLAIN ANALYZE output looks like noise the first several times you read it. It stops being noise once you know it's answering a small, fixed set of questions, in a specific order.

Start with the outermost node's actual time

The very first thing to look at isn't the deepest, most complicated-looking line — it's the actual time on the outermost node, which tells you the total execution time for the whole query. Everything else in the plan is explaining why that number is what it is. Scanning for the scariest-looking nested loop before checking whether the query is even slow is a common way to spend twenty minutes optimizing something that didn't need it.

Estimated rows vs actual rows

Every node reports both an estimated row count and an actual row count. When these are close, the planner had good statistics to work with and probably made a reasonable choice. When they're wildly off — the planner expected 50 rows and got 50,000 — that's usually the actual root cause of a bad plan, not the specific join strategy it picked. A join strategy chosen for 50 rows is often a bad strategy for 50,000, and the fix is frequently ANALYZE-ing the table to refresh statistics, not rewriting the query.

Seq Scan is not automatically the villain

A sequential scan gets blamed reflexively, but it's the right choice for a query that's going to touch most of the table anyway — an index scan that still has to fetch most rows just adds index-traversal overhead on top of reading the table. The question worth asking isn't "why is there a Seq Scan," it's "is this query actually selective enough that an index should help," and if the answer is yes, whether an index exists on the right column, and whether ANALYZE has run recently enough for the planner to trust it.

Reading the tree

The plan is a tree, and it executes from the innermost nodes outward — the deepest node runs first, feeding its output up to the node above it. Reading top-down (which is how the output is printed) tells you the shape of the plan; reading bottom-up tells you the actual order of execution. Both readings matter: top-down for understanding the overall strategy, bottom-up for finding which specific step is where the time actually goes, which is usually the node with the largest gap between its own actual time and its children's actual time combined.

The fastest way to get comfortable with this is running EXPLAIN ANALYZE on queries you already understand the performance of, so the numbers have something to calibrate against before you need to trust the plan on a query you don't yet understand.

Tags

PostgreSQLSQLPerformance