Free developer tool

PostgreSQL EXPLAIN ANALYZE visualizer

Paste a query plan and instantly see the execution tree, where the time actually goes, and exactly what to fix - sequential scans, row misestimates, on-disk sorts, and runaway nested loops. Free, deterministic, and 100% in your browser.

  • Runs in your browser No server round-trip
  • No sign-up Nothing to install
  • Nothing is uploaded Your SQL never leaves the tab
  • Works offline After the page loads

Parsed entirely in your browser. Nothing is uploaded.

What this does

Turn a wall of plan text into an answer

EXPLAIN ANALYZE is the ground truth for why a Postgres query is slow - but the raw text is dense, and the important number (self time) is buried. This visualizer parses the plan, ranks nodes by the time they actually consume, and applies the same rules an experienced DBA would, with the reasoning attached.

  • Interactive, collapsible execution tree with per-node timing and cost.
  • Slow-node highlighting by self time - the time a node spends on itself, not its children.
  • Estimated-vs-actual row checks that catch the misestimates behind most bad plans.
  • Detection for sequential scans, nested-loop blow-ups, bitmap/hash spills, and external sorts.
  • Plain-English fixes you can copy into a pull request or ticket.
How to read it

A plan is a tree that executes bottom-up

The deepest nodes run first and pass their rows up to their parents. The top node's actual time is the whole query; each child's self time tells you where that total is really spent.

Limit / Resultreturns final rowsHash Joincombines two inputsSortorders the rowsIndex Scanruns firstSeq Scanruns firstrows flow up
Node reference

The scan and join types you'll see

Every node in a plan is one of a handful of operations. Knowing what each one implies is most of the battle.

Node type
What it does
Usually good when
Watch out when
Seq ScanReads every row in the tableThe table is small or you need most rowsThe table is large and you need a few rows
Index ScanWalks an index, then fetches matching rowsSelective filter, few matching rowsReturning most of the table (random I/O adds up)
Index Only ScanAnswers from the index aloneAll needed columns are in the indexThe table isn't recently vacuumed (heap fetches)
Bitmap Heap ScanCollects matches, then reads pages in orderMedium selectivity, multiple conditionsRechecks appear (bitmap went lossy - low work_mem)
Nested LoopFor each outer row, probe the inner sideInner side is tiny or index-backedInner side runs thousands of loops
Hash JoinBuilds a hash of one side, probes with the otherJoining large, unsorted setsBatches > 1 (hash spilled to disk)
Merge JoinMerges two pre-sorted inputsBoth inputs already sorted on the keyA sort has to be added just to feed it
Get it right

Common mistakes when reading a plan

01

Optimising by total time, not self time

The top node's total time includes all its children. Chasing it leads you in circles. Sort by self (exclusive) time - the tool does this for you - to find the node that is genuinely expensive.

02

Trusting the cost numbers as milliseconds

Cost is a unitless planner estimate, not time. A high-cost node can be fast and a low-cost node slow. Once you have ANALYZE output, judge by actual time and rows, not cost.

03

Ignoring a big row misestimate

If a node planned for 10 rows and returned 100,000, everything above it is built on a wrong assumption. Fix the estimate (ANALYZE, statistics) before you touch indexes.

04

Adding an index without re-checking the plan

An index only helps if the planner uses it. After creating one, re-run EXPLAIN ANALYZE and confirm the seq scan became an index scan and the time dropped.

05

Testing on a tiny dev dataset

On 100 rows a seq scan always wins, so the plan you see locally is not the plan production runs. Analyse against production-sized data or a realistic copy.

06

Forgetting BUFFERS

Without BUFFERS you can't tell a cache hit from a disk read. EXPLAIN (ANALYZE, BUFFERS) is the version worth pasting - the extra numbers explain a lot of otherwise-mysterious timing.

EXPLAIN ANALYZE FAQ

What is the difference between EXPLAIN and EXPLAIN ANALYZE?

EXPLAIN shows the planner's chosen plan with estimated costs and row counts, without running the query. EXPLAIN ANALYZE actually executes the query and adds the real timing and real row counts for every node, so you can compare the estimate to reality. Always use ANALYZE when you are diagnosing a slow query - the estimates alone can be very wrong.

Is my query plan uploaded anywhere?

No. The plan is parsed entirely in your browser with JavaScript. Nothing is sent to a server, logged, or stored. You can disconnect from the internet after the page loads and the tool still works, which makes it safe to paste plans from production systems.

How do I read a PostgreSQL execution plan?

Read it inside-out and bottom-up. The most indented nodes run first and pass their rows up to their parent. Each node shows cost (planner estimate), actual time, and actual rows. The node where the most time is spent - its self time, not the total - is usually the one to optimise.

Why is my row estimate so different from the actual rows?

Postgres estimates row counts from table statistics gathered by ANALYZE. If the statistics are stale, or columns are correlated, the estimate can be off by orders of magnitude. A large misestimate is the single most common cause of a bad plan, because it leads the planner to pick the wrong join type and order. Run ANALYZE on the table, and use CREATE STATISTICS for correlated columns.

What is a sequential scan and is it always bad?

A sequential scan reads every row in the table. It is not always bad - for a small table, or when a query genuinely needs most of the rows, it is the fastest option. It becomes a problem when the table is large and the query only needs a few rows, because an index would let Postgres jump straight to them. This tool flags seq scans that discard most of the rows they read.

Should I add BUFFERS to my EXPLAIN?

Yes. EXPLAIN (ANALYZE, BUFFERS) adds shared/local/temp buffer counts, which tell you whether a node read from the cache or from disk. High 'read' (as opposed to 'hit') numbers point at data that is not cached, and temp buffers reveal sorts and hashes spilling to disk. This tool understands BUFFERS output.

Does this work with pgAdmin, psql, or Postgres logs?

Yes. Paste the text-format plan from psql, pgAdmin's Explain tab (as text), auto_explain log output, or a managed provider's console. The parser tolerates the aligned table formatting psql adds. JSON-format plans are on the roadmap.

Tuned your query? Run it on Postgres that's already fast.

Swyftstack gives you managed PostgreSQL 16 with SSL, daily backups, and a one-click console - deployed in under a minute. The same query, on infrastructure you don't have to babysit.

Free tools. Serious infrastructure.

Deploy a production PostgreSQL database in under a minute. No credit card for the 14-day trial.