API, Data & Developer Tools

SQL Explain Plan Visualizer

Render PostgreSQL EXPLAIN (JSON or text) and MySQL EXPLAIN FORMAT=JSON as an operator tree, compare estimated and actual rows, and explain the costliest nodes.

  • Operator tree
  • Estimate vs actual
  • Hot nodes explained
Runs in your browser

Everything you paste, type or drop is processed in this browser tab. It is not uploaded, logged, stored or sent to analytics.

Plan visualizer workspace

1 Your plan

Examples:

PostgreSQL text or JSON (with or without ANALYZE), or MySQL EXPLAIN FORMAT=JSON. psql headers and row counts are stripped. Up to 5 MB and 5,000 nodes.

How to capture a plan

PostgreSQL: EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) SELECT ...; - ANALYZE really runs the statement, so wrap data-changing statements in BEGIN; ... ROLLBACK;. MySQL 8: EXPLAIN FORMAT=JSON SELECT ...; (estimates only).

2 Operator tree

Paste a plan and choose Visualise plan, or load an example.

What the SQL Explain Plan Visualizer does

This visualiser turns PostgreSQL EXPLAIN output - text or JSON, with or without ANALYZE - and MySQL EXPLAIN FORMAT=JSON into a readable operator tree. For each node it shows exclusive time or cost, estimated against actual rows, loops and its share of the whole, then names the nodes worth looking at first: the slowest, the worst estimates, sorts that spilled to disk and scans repeated inside a nested loop.

It reads the plan as text in your browser. It never connects to a database and never runs your query.

How to use it

  1. Capture a plan. In PostgreSQL, EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) SELECT ... gives the most detail; the default text format from psql works too. In MySQL 8, use EXPLAIN FORMAT=JSON SELECT ....
  2. Paste the whole output. psql's QUERY PLAN header, dashed rule, row count footer and trailing + characters are removed automatically.
  3. Select Visualise plan. Read the findings first, then follow the tree: the highlighted nodes are the three with the largest exclusive time (or cost, without ANALYZE).
  4. Open a node's Details for its conditions, sort keys and buffer counts, or sort the node table by exclusive time or misestimate.
  5. Copy or download the summary to attach it to a ticket, or the node table as CSV.

Reading the results

PostgreSQL reports actual time and rows as averages per loop. A node that ran 4,800 times with actual time=0.020..0.024 rows=1 took 0.024 x 4,800 = 115.2 ms and returned 4,800 rows in total. The visualiser multiplies by loops before comparing anything - reading the per-loop figures directly is the most common mistake with EXPLAIN ANALYZE.

Exclusive time is a node's own work: its inclusive time minus its children's. A slow node near the top is often just waiting for a slow child; the exclusive figure shows where the time is actually spent.

The misestimate factor is the larger of estimated and actual rows divided by the smaller. At 3x it is flagged as a warning and at 10x as a problem, because the planner chooses join methods and join order from these estimates. Overestimates under a LIMIT are expected - the executor stops early - and are not flagged.

MySQL's JSON format holds only estimates and costs (read_cost plus eval_cost per table); there are no actual rows or times, so nodes are ranked by estimated cost and the page says so.

Worked example: a paid-orders report with a spilled sort

The PostgreSQL ANALYZE example joins orders to customers and sorts by date. The whole query took 49.880 ms. The Sort's inclusive time is 49.004 ms and its child, the Hash Join, took 41.870 ms, so the sort itself cost 49.004 - 41.870 = 7.134 ms - and its Sort Method line says external merge Disk: 520kB, so it spilled to disk.

The Hash Join's own time is 41.870 - 35.600 - 1.211 = 5.059 ms. The Seq Scan on orders took 35.600 ms, 72.6% of the root node's 49.004 ms, and discarded 41,588 rows to keep 8,412: the obvious candidate for an index on status.

The planner expected 1,000 paid orders and got 8,412, an 8.4x underestimate that carries up through the join and the sort. Refreshing statistics with ANALYZE orders is the first thing to try; a larger work_mem for this query would stop the sort spilling.

Formulas and scoring rules

Totals across loops (PostgreSQL)
inclusive ms = actual total time x loops; total rows = actual rows x loops; estimated total = plan rows x loopsPostgreSQL documents actual time and rows as per-loop averages.
Exclusive time
exclusive = inclusive - sum(children's inclusive)Floored at 0, because parallel workers and InitPlans can make it slightly negative.
Misestimate factor
factor = max(est, act) / max(min(est, act), 1)3x or more is a warning, 10x or more a problem. Shown to one decimal place.
Self cost without ANALYZE
self cost = total cost - sum(children's total cost)Costs are the planner's arbitrary units, not milliseconds.

What the flags mean

A Seq Scan that removes far more rows than it keeps reads the whole table to find a few rows; an index on the filter columns usually helps. A Nested Loop whose inner side is a Seq Scan repeated thousands of times is worse: the table is read once per outer row.

A sort that reports an external merge, or a hash with more than one batch, ran out of work_mem and used temporary files. A lossy bitmap heap scan (rows removed by index recheck) did the same for its bitmap.

In MySQL, access_type: ALL is a full table scan, using_filesort means the result is sorted rather than read in index order, and a low filtered percentage means most examined rows are thrown away by the attached condition.

Running EXPLAIN ANALYZE safely

ANALYZE executes the statement for real. For INSERT, UPDATE or DELETE, wrap it in BEGIN; and ROLLBACK; so the changes are undone, and remember that it still takes locks and does the work while it runs. On a busy production database, capture plans from a replica or a recent copy where you can.

Limitations: what the result does not prove

  • It explains a plan; it does not produce one. Plans change with data, statistics, settings and parameter values, so one captured plan describes one execution.
  • MySQL EXPLAIN FORMAT=TREE and EXPLAIN ANALYZE output, SQL Server showplan XML and Oracle plans are not read. Use MySQL's FORMAT=JSON here.
  • Timings from EXPLAIN ANALYZE include instrumentation overhead, which can be noticeable for nodes that run millions of times. Compare nodes within a plan rather than trusting absolute figures.
  • Findings are rules of thumb. A sequential scan is often the right choice for a small table or a query that needs most of its rows.

Privacy: where your data goes

Everything you paste, type or drop is processed in this browser tab. It is not uploaded, logged, stored or sent to analytics. Session recording and tag-manager scripts are switched off on this page.

Standards and sources

Frequently asked questions

Why are the actual rows in my plan smaller than the rows the query returned?

Because PostgreSQL prints actual rows per loop. An inner node that ran 500 times and shows rows=3 produced 1,500 rows. The visualiser multiplies every figure by loops, so its estimated and actual columns are comparable totals.

What is the difference between inclusive and exclusive time?

Inclusive time is what the node reports: its own work plus everything beneath it. Exclusive time subtracts the children, leaving only the node's own work. Hot nodes are ranked by exclusive time so a parent is not blamed for a slow child.

How bad does a row estimate have to be before it matters?

Small errors are normal. Errors of ten times or more often change the plan - a nested loop chosen for what the planner thought were 12 rows but were 4,800 is a classic slow query. Fix them with ANALYZE, a higher statistics target, or extended statistics for correlated columns.

Can I paste EXPLAIN output without ANALYZE?

Yes. Without ANALYZE there are no actual times or rows, so the tree shows each node's self cost and estimated rows, and hot nodes are ranked by cost. That is useful for spotting a missing index but cannot show misestimates.

Why does MySQL EXPLAIN FORMAT=JSON show no actual timings?

MySQL's JSON explain format contains only the optimiser's estimates: rows examined per scan, the filtered percentage and read and evaluation costs. Actual figures come from EXPLAIN ANALYZE, which prints a tree format this page does not read.

Does pasting a plan send my SQL or table names anywhere?

No. The plan is parsed as text in this tab and nothing is uploaded. Plans can still contain literal values from your query, so check before sharing the downloaded summary with anyone else.

Last reviewed by the A2Z.Tools team against the sources listed above.

Rate this tool

Was this tool useful? Your feedback helps us improve it.

No ratings yet — be the first to rate this tool.
Your rating (required)
0 / 2000

Please do not include passwords, payment details or other sensitive information.

Your feedback is sent privately to the A2Z.Tools team and will not be posted publicly.