After rewriting a slow query, how do you use plans to prove the rewrite actually helped?
answer
- three separate claims, not one
- same rows first, speed second
- the first run pays for cold caches
- name the plan node that changed
- internal cost units are not milliseconds
basics
~20 sCompare measured plans, not estimates: run both versions with EXPLAIN ANALYZE on the same data and parameters, discard the first cold run, and confirm both queries return identical result sets before trusting any timing difference.
solid answer
~60 sThree things have to hold before a rewrite counts as an improvement. **Equivalence** — the new query returns exactly the same rows for the same inputs; verify it, for instance by comparing both directions of an `EXCEPT` (empty both ways) plus the row counts, not by eyeballing the first page. **Measurement** — run both with the analyze form on the same data, the same parameter values, and after a warm-up run, because the first execution pays for cold caches and will flatter whichever query you ran second. **Mechanism** — check the plan changed for the reason you intended: the predicate you reshaped now positions the index scan, the discarded-rows count collapsed, the sort you targeted is gone. A rewrite that is faster for a reason you cannot name is a coincidence waiting to reverse. Finally, do not compare estimated cost numbers between two queries as a proxy for speed — they are internal units, not milliseconds. And re-check at production scale with a worst-case parameter value, since a plan that wins on a typical value can lose on a skewed one.
go deeper
Know that a faster-looking query must first be proven to return the same rows, and that a single timing on a warm cache is not evidence.
Be able to run both versions with the measured form of EXPLAIN on the same data and parameters, discard the cold run, and point at the plan difference that explains the gain.
Own the method end to end: equivalence check, fair measurement including a worst-case parameter, a named mechanism in the plan, one variable at a time, and confirmation in production telemetry.
Set the bar for how performance changes are justified and recorded, so a rewrite arrives with its before-and-after evidence and the team can tell later whether it is still earning its complexity.
## The claim you are making "I rewrote it and it is faster" is three claims at once: the query still answers the same question, it really is faster, and it is faster for a reason that will keep holding. Plans plus measurements let you support all three; each on its own supports none. ## Claim 1: it still returns the same rows This is the one people skip, and it is the one that causes incidents. A rewrite that drops a duplicate, changes NULL handling or reorders a limit is not faster — it is wrong. Verify it mechanically on a representative data set: compare both directions of a set difference and require both to be empty, and compare the row counts, so that duplicate multiplicity is covered too. Run the check for the parameter values that exercise the interesting branches, including one where the result is empty and one where a joined side has no match. ## Claim 2: it really is faster Measure, do not infer: - **Use the measured form of EXPLAIN**, not the estimate-only form. Estimated cost is an internal number on an arbitrary scale; it does not convert to time and is not comparable across two different queries as evidence of speed. - **Same data, same parameters.** Comparing yesterday's slow run on production against today's fast run on a snapshot proves nothing. - **Discard the first run of each.** The first execution pays cold-cache costs; the second query in a session inherits a warm cache and looks better than it is. Alternate the order and take the steadier of several runs. - **Test the worst case, not just the typical one.** Rewrites frequently win on a value that matches ten rows and lose on the value that matches a million. If the query is parameterised, measure both. ## Claim 3: it is faster for the reason you think This is what the plan adds beyond a stopwatch. Before the change, you should be able to point at the offending part of the plan — a predicate applied as a filter with a huge discarded-row count, a scan where you expected an index, an extra sort. After the change, that specific thing should be different: the predicate now positions the index scan, the discarded count has collapsed, the sort node is gone. If the plan looks structurally identical and the query merely ran faster, you measured cache warmth, not an improvement. ## Change one thing at a time A rewrite bundled with a new index and a statistics refresh leaves you unable to attribute the gain. You will then carry all three forever, including the two that did nothing. Apply them separately, re-read the plan after each, and keep only what moved the number. ## Know what you cannot conclude locally Plans depend on data volume, value distribution and the parameters in play, so a laptop result is a hypothesis, not a verdict. Confirm on production-like data, and after shipping, watch the real latency for the endpoint or job that runs the query. The final evidence for a performance change is the production metric, not the plan that predicted it. ## Write it down When the rewrite ships, put the before-and-after plan fragments and the reason in the change description. Six months later, someone will "simplify" the query back to the readable original; the recorded mechanism is what stops that, and it is also what tells you whether the change is still needed after the schema moves on. ## What interviewers listen for The strong answer is a method, not a tool name: prove equivalence, measure fairly with warm runs and realistic parameters, identify the specific plan change that explains the gain, isolate one variable at a time, and confirm in production. Candidates who only say "the cost went down" have never had a rewrite regress on them.
- How do you check that the rewrite returns exactly the same rows?Run the set difference in both directions on a representative data set and require both to be empty, then compare the row counts so duplicate multiplicity is covered too. Do it for parameter values that exercise the interesting branches, including one that returns nothing and one where a joined side has no match.
- Why is comparing the two queries' estimated costs a weak argument?Estimated cost is an internal unit on an arbitrary scale, produced before anything ran and derived from estimates that can be wrong. It does not convert to time and carries no guarantee across two differently-shaped queries. Measured execution on the same data and parameters is the evidence.
- Your rewrite is faster on a copy but not in production. What differs?Typically data volume and value distribution, the actual parameter values in traffic, cache warmth, and concurrency. Re-measure at production scale with real parameter values, including a skewed worst case, and confirm against the endpoint's own latency rather than a single hand-run query.
saying these in an interview costs you the question
- Declares victory because the estimated cost number dropped
- Compares a cold run of the old query with a warm run of the new one
- Ships a rewrite without checking the result set is unchanged
- Cannot name which plan node changed and why
- Bundles a rewrite, a new index and a statistics refresh as one change