What is PostgreSQL explain telling me exactly?
Explaining_EXPLAIN.pdf could help too.
The part I always found confusing is the startup cost vs total cost. I Google this every time I forget about it, which brings me back to here, which doesn't explain the difference, which is why I'm writing this answer. This is what I have gleaned from the Postgres
EXPLAIN documentation, explained as I understand it.
Here's an example from an application that manages a forum:
EXPLAIN SELECT * FROM post LIMIT 50; Limit (cost=0.00..3.39 rows=50 width=422) -> Seq Scan on post (cost=0.00..15629.12 rows=230412 width=422)
Here's the graphical explanation from PgAdmin:
(When you're using PgAdmin, you can point your mouse at a component to read the cost details.)
The cost is represented as a tuple, e.g. the cost of the
cost=0.00..3.39 and the cost of sequentially scanning
cost=0.00..15629.12. The first number in the tuple is the startup cost and the second number is the total cost. Because I used
EXPLAIN and not
EXPLAIN ANALYZE, these costs are estimates, not actual measures.
- Startup cost is a tricky concept. It doesn't just represent the amount of time before that component starts. It represents the amount of time between when the component starts executing (reading in data) and when the component outputs its first row.
- Total cost is the entire execution time of the component, from when it begins reading in data to when it finishes writing its output.
As a complication, each "parent" node's costs includes the cost's of its child nodes. In the text representation, the tree is represented by indentation, e.g.
LIMIT is a parent node and
Seq Scan is its child. In the PgAdmin representation, the arrows point from child to parent — the direction of the flow of data — which might be counterintuitive if you are familiar with graph theory.
The documentation says that costs are inclusive of all child nodes, but notice that the total cost of the parent
3.39 is much smaller than the total cost of it's child
15629.12. Total cost is not inclusive because a component like
LIMIT doesn't need to process its entire input. See the
EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 100 AND unique2 > 9000 LIMIT 2; example in Postgres
In the example above, startup time is zero for both components, because neither component needs to do any processing before it starts writing rows: a sequential scan reads the first row of the table and emits it. The
LIMIT reads its first row and then emits it.
When would a component need to do a lot of processing before it can start to output any rows? There are a lot of possible reasons, but let's look at one clear example. Here's the same query from before but now containing an
ORDER BY clause:
EXPLAIN SELECT * FROM post ORDER BY body LIMIT 50; Limit (cost=23283.24..23283.37 rows=50 width=422) -> Sort (cost=23283.24..23859.27 rows=230412 width=422) Sort Key: body -> Seq Scan on post (cost=0.00..15629.12 rows=230412 width=422)
Once again, the sequential scan on
post has no startup cost: it starts outputting rows immediately. But the sort has a significant startup cost
23283.24 because it has to sort the entire table before it can output even a single row. The total cost of the sort
23859.27 is only slightly higher than the startup cost, reflecting the fact that once the entire dataset has been sorted, the sorted data can be emitted very quickly.
Notice that the startup time of the
23283.24 is exactly equal to the startup time of the sort. This is not because
LIMIT itself has a high startup time. It actually has zero startup time by itself, but
EXPLAIN rolls up all of the child costs for each parent, so the
LIMIT startup time includes the sum startup times of its children.
This rollup of costs can make it difficult to understand the execution cost of each individual component. For example, our
LIMIT has zero startup time, but that's not obvious at first glance. For this reason, several other people linked to explain.depesz.com, a tool created by Hubert Lubaczewski (a.k.a. depesz) that helps understand
EXPLAIN by — among other things — subtracting out child costs from parent costs. He mentions some other complexities in a short blog post about his tool.