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.
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.
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.
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 Scan | Reads every row in the table | The table is small or you need most rows | The table is large and you need a few rows | |
| Index Scan | Walks an index, then fetches matching rows | Selective filter, few matching rows | Returning most of the table (random I/O adds up) | |
| Index Only Scan | Answers from the index alone | All needed columns are in the index | The table isn't recently vacuumed (heap fetches) | |
| Bitmap Heap Scan | Collects matches, then reads pages in order | Medium selectivity, multiple conditions | Rechecks appear (bitmap went lossy - low work_mem) | |
| Nested Loop | For each outer row, probe the inner side | Inner side is tiny or index-backed | Inner side runs thousands of loops | |
| Hash Join | Builds a hash of one side, probes with the other | Joining large, unsorted sets | Batches > 1 (hash spilled to disk) | |
| Merge Join | Merges two pre-sorted inputs | Both inputs already sorted on the key | A sort has to be added just to feed it |
Common mistakes when reading a plan
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.
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.
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.
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.
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.
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.
Related developer tools
More free, browser-only tools for PostgreSQL and cloud storage.
Build or decode a DATABASE_URL and generate ready-to-paste snippets for Node, Python, Go, Rust, Java, and PHP.
Open toolTurn CPU, RAM, and workload into a safe pool_size, max_connections, PgBouncer mode, and timeouts - with the math shown.
Open toolFind a safe max_connections and shared_buffers from your instance RAM, with per-connection memory accounted for.
Open toolCompare AWS S3, Cloudflare R2, Backblaze B2, Google Cloud, Azure, and Supabase side by side - storage, egress, and requests, with charts.
Open toolTuned 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.