What this tool does
PostgreSQL's EXPLAIN ANALYZE tells you exactly how a query was executed โ but the raw output is famously hard to read. Timings are inclusive (each node's time contains all of its children), row counts are averaged per loop, and the real bottleneck is often buried three levels deep in the tree.
This visualizer parses the plan and shows, for every node:
- Exclusive time โ the time spent in the node itself, computed as its total time (ร loops) minus the time of its children, with a colored bar proportional to its share of the whole query.
- Estimated vs actual rows โ the planner's row estimate next to what actually came out, with the mis-estimation factor when they diverge.
- Buffers, filters, sort and hash details when they are present in the plan.
How to get the JSON plan
Run your query with:
EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON)
SELECT ... your query ...;
and paste the result above. BUFFERS is optional but recommended โ it shows how much I/O each node performed. You can paste directly from psql: the QUERY PLAN header and the + line-continuation characters are cleaned up automatically. Plain-text EXPLAIN format is not supported yet.
โ ๏ธ EXPLAIN ANALYZE really executes the query. For an INSERT/UPDATE/DELETE, wrap it in BEGIN; ... ROLLBACK;.
Warnings this tool detects
- Row estimate off by more than 10ร โ the planner expected a very different number of rows than it got. Bad estimates cascade into bad join strategies. Typical fixes: run
ANALYZEon the table, raise the column's statistics target, or add extended statistics for correlated columns. - Sequential scan over many rows with a filter โ the executor read the whole table to keep only part of it. Often a sign that an index would help.
- Sort spilling to disk โ
Sort Method: external mergemeans the sort did not fit inwork_memand used temporary files. - Hash spilling to disk โ a Hash node using more than one batch had to write partitions to disk; the hash table did not fit in
work_mem. - Expensive nested loop โ the inner side of a Nested Loop was executed many times and accounts for a significant share of the total runtime.
- Many rows removed by a filter โ the node produced far fewer rows than it had to examine, suggesting a missing or poorly selective index.
- Node never executed is also flagged (loops = 0) so zero-time nodes don't confuse the reading.
Reading the tree
Execution starts at the leaves (scans) and flows up to the root. The horizontal bar on each node is its exclusive time share: a red bar at a leaf Seq Scan means the time is really spent scanning, not in the join above it. Click a node header to expand its details โ costs, row counts, filter conditions, buffer usage โ and use Copy text summary to paste a compact report into a ticket or a chat.
Note on parallel queries: timing attribution across parallel workers is an approximation โ children of a Gather node report loops per worker, so exclusive times there should be read as indicative.