Most bad tuning decisions come from acting on a plan nobody fully read. Paste the actual plan into an LLM and get a plain-language walkthrough — scan types, join order, where the time went, where the estimates lied — so you understand the query before you touch an index or a hint.
Tier: Foundation · Workflow area: SQL Authoring & Query Tuning · ~25 minutes
Execution plans are the most information-dense artifact in database work and the least read. This playbook uses AI as a plan translator: it turns a nested tree of operators into a narrative you can verify node by node. The output is understanding, not action — changes come later (pb-03), and only with before/after proof.
ANALYZE (actual) over estimated plans.Capture the actual plan with runtime numbers. Estimates alone hide the most important signal — the estimate-vs-actual gap:
EXPLAIN (ANALYZE, BUFFERS, VERBOSE, FORMAT TEXT)
SELECT c.region, count(*), sum(ol.amount)
FROM orders o
JOIN order_lines ol ON ol.order_id = o.id
JOIN customers c ON c.id = o.customer_id
WHERE o.ordered_at >= now() - interval '90 days'
GROUP BY c.region;
If the query is too slow or too dangerous to run with ANALYZE, say so and use plain EXPLAIN — but tell the model it is looking at estimates only.
Prompt for a structured walkthrough, not a verdict. The failure mode is an LLM that skips to "add an index." Force the reading first:
You are a senior PostgreSQL performance engineer. Below is an actual
execution plan (EXPLAIN ANALYZE, BUFFERS) from PostgreSQL 16.
Walk it in four parts, in order, before any recommendations:
1. NARRATIVE — bottom-up: what is read, how, in what join order,
in plain language a developer can follow.
2. TIME — which nodes account for the actual time, with the numbers.
3. ESTIMATE GAPS — every node where estimated rows and actual rows
differ by more than 10x, and what each gap implies.
4. BUFFERS — where the reads happen; shared hit vs read ratio.
Only then: 5. up to three hypotheses worth testing, each phrased as
"test X, expect Y" — no DDL, no config changes.
Plan:
[paste plan]
Verify the narrative against the tree. Spot-check three claims minimum: the node the AI says is hottest really carries the largest actual time; the join order it describes matches the tree nesting; the row counts it quotes exist in the plan text. A good verified summary reads like:
Hot node: Parallel Seq Scan on order_lines
actual time 2841ms of 3105ms total ✓ (matches plan line 7)
Estimate gap: index scan on orders — est. 1,240 rows, actual 386,554
(312x) ✓ — planner chose nested loop on a bad estimate
Buffers: shared read 412,009 vs hit 18,441 — cold, I/O-bound ✓
Interrogate the biggest estimate gap. Estimate gaps are usually stale statistics, correlated columns, or a non-sargable predicate. Ask directly, then confirm on the database:
-- When were stats last refreshed on the misestimated table?
SELECT relname, last_analyze, last_autoanalyze, n_live_tup, n_mod_since_analyze
FROM pg_stat_user_tables
WHERE relname IN ('orders', 'order_lines');
-- Does the predicate defeat the index? (functions, casts, mismatched types)
SELECT attname, atttypid::regtype
FROM pg_attribute
WHERE attrelid = 'orders'::regclass AND attname = 'ordered_at';
If n_mod_since_analyze is a large fraction of n_live_tup, run ANALYZE orders; on a copy or replica and re-capture the plan before believing anything else.
Write the two-line diagnosis. Compress the walkthrough into the ticket: time goes to X because Y; the leading hypothesis is Z, testable by W. Example: "93% of time is a seq scan on order_lines driven by a nested loop chosen on a 312x row misestimate; hypothesis: stale stats on orders — re-ANALYZE and re-plan before considering indexes."
Park the hypotheses, don't execute them. The output of this playbook is understanding plus a ranked test list. Execution belongs to the tuning loop with before/after verification (pb-03).
SET STATISTICS XML ON), paste the XML or the plan summary, and ask the same four-part walkthrough; estimate gaps show as Estimated vs Actual Number of Rows per operator, and SET STATISTICS IO ON output stands in for BUFFERS.EXPLAIN ANALYZE (8.0.18+) for the tree with actual timings; there is no buffers section, so pair it with SHOW STATUS LIKE 'Handler%' deltas around the query run.SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL, NULL, 'ALLSTATS LAST')) after running with /*+ GATHER_PLAN_STATISTICS */; E-Rows vs A-Rows is your estimate-gap column.ANALYZE is cheaper than DDL.Plans embed literal predicate values — customer IDs, emails, account numbers can appear in filter conditions. Scrub literals or use parameterized plan captures before pasting into an external assistant.
A plan walkthrough you have verified node-by-node turns tuning from guesswork into hypothesis testing — the AI reads faster than you, but only the plan text itself gets to be the authority.