PostgreSQL EXPLAIN Analyzer

Paste the JSON plan from EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) to see its tree and the hotspots this page can spot locally. The plan is parsed in your browser; the query is never executed and no database is contacted.

Runs locally in your browser
This tool processes all data locally in your browser.
EXPLAIN JSONPaste the JSON result; the database is never contacted.

How to read a PostgreSQL execution plan from EXPLAIN JSON

Paste the JSON output of EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) and press Analyze plan. The page draws the plan tree, totals the nodes, cost, actual time and rows, and lists the hotspots it can recognise locally.

The plan is parsed in the browser; no statement is executed and nothing is sent to a database or to this site. Only the JSON format is read — the text format from psql is not a substitute.

  1. Run your statement with EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) — with ANALYZE, otherwise there are no actual rows or times to compare.
  2. Paste the whole result (the outer array is fine) into the box and press Analyze plan.
  3. Read the summary first: node count, highest total cost, execution time and the rows returned at the root.
  4. Check the findings list: estimate misses, rows removed by a filter, inner sides run many times and the slowest node are the ones worth acting on.
  5. Fix one thing at a time (an index, a rewrite, a statistics target) and re-run the plan to see whether the numbers move.

What the analyzer reports, what it cannot see, and how to use it

What the analyzer reports

The summary shows the number of plan nodes, the highest Total Cost in the tree, the execution time (Execution Time when present, otherwise the slowest node) and the rows the root node returned. The tree under it repeats cost, actual time, rows, loops, the estimate and the index condition or filter for every node.

The findings list compares Plan Rows with the rows actually returned: when they differ by ten times or more, the node is named with both numbers. It also reports a filter that removed at least ten times more rows than it returned, an inner side executed a thousand times or more, and a slowest node that takes at least 50 ms and at least a fifth of the execution time.

What it cannot see

A plan without ANALYZE has no actual rows or timings, so the page says so and leaves the timing metrics out instead of printing zeros; estimate checks are skipped for the same reason. The BUFFERS counters are shown in the JSON but not interpreted here, and JIT, parallel worker distribution and trigger or function time are not analysed.

One plan is one execution: a warm cache, different statistics, a different parameter value or a prepared statement reusing a generic plan can all produce a different tree. Compare plans taken under the same conditions, and treat the findings as pointers to inspect rather than as a verdict.

Acting on the result

A large estimate miss usually means stale statistics or a predicate the planner cannot estimate (a function on the column, a correlated condition, an unusual data distribution); ANALYZE, a statistics target or a rewritten predicate often changes the chosen plan before any index is needed.

Rows removed by a filter point at a missing or unused index and at a predicate that is not selective enough, while a repeated inner side of a nested loop is the classic sign of a join that a hash join would handle better. Change one thing, re-run with the same parameters, and keep the plan that wins on the workload you actually run.

Recent tools: