0%
Database Internals: Build the Engine
Storage Structures
Durability
Concurrency Control
Access Paths
Distribution
Cost Models and Statistics
The planner has to choose between plans without running them, which means it needs a way to score a plan from its shape alone, and understanding what that score is and is not explains most of what the planner does.
What the model is for
Comparing candidate plans against each other so the cheapest can be chosen, which is the only question the number is built to answer.
Starting with the purpose avoids the most common misunderstanding. The model exists to rank alternatives, and it is designed for that and for nothing else.
Why the cost is not a time prediction
It is expressed in arbitrary units calibrated so that the relative sizes are meaningful, and nothing in it corresponds to a duration.
Making this explicit early matters because the number looks like one. A cost of a thousand does not mean a thousand of anything, and comparing a cost against a measured duration is meaningless.
Why arbitrary units are sufficient
Choosing between two plans needs only to know which is smaller, so any scale that preserves the ordering is as good as any other.
Justifying the design is worthwhile because it seems like a weakness. The model gives up predicting duration in exchange for not needing to know anything about the machine it is running on.
What the cost is assembled from
A count of pages to be read multiplied by a parameter for what a page read costs, plus a count of rows to be processed multiplied by a parameter for per-row work.
Decomposing it is the useful move because it separates the two sources of error. The parameters are configured and stable, and the counts are estimated and can be wildly wrong.
Why the counts matter more than the parameters
A parameter that is off by a factor of two shifts every plan's cost similarly, while a row count off by a factor of a thousand changes which plan wins entirely.
Stating the asymmetry is the most important idea in the section. Almost every bad plan is a bad estimate rather than a badly configured model, which is where attention should go.
Why there are separate parameters for different page reads
Reading pages in order is much faster than reading scattered pages on most storage, so the model distinguishes the two and charges them differently.
Covering the distinction is necessary because it drives index decisions. The gap between the two parameters is what decides whether an index lookup across many rows beats reading everything in order.
Why that gap matters on modern storage
The defaults in most engines were chosen when the gap was very large, and on solid state storage it is much smaller, which biases plans away from indexes.
Naming the practical consequence is valuable. Adjusting the ratio for the actual storage is one of the few cost model changes that is reliably worth making.
What the model assumes about caching
Some fraction of pages will already be in memory, which engines handle either with a configured expectation or by tracking what is actually resident.
Including the caching assumption is necessary because it is substantial. A table entirely in memory costs nothing like a table on disk, and the model has to account for that somehow.
Why the caching assumption is usually crude
Tracking true residency per table makes plans depend on momentary memory contents, which would make the same query plan differently from minute to minute.
Explaining the trade is worthwhile because the crudeness is deliberate. Stable plans built on an approximate assumption are preferable to volatile plans built on an accurate one.
Why plan stability is valued at all
An application whose plans change unpredictably is one whose performance cannot be reasoned about, so engines prefer a slightly worse plan that stays the same.
Making the value explicit is worth doing because it explains several design choices. The planner is optimising for a workload over time rather than for one execution.
What the model deliberately ignores
Concurrency, contention for memory, the state of the storage queue and anything else that depends on what else is happening at that moment.
Listing the omissions is useful because they are sometimes the actual cause. A plan that is correct in isolation can be the wrong choice under load, and the model has no way to know that.
Why memory available for a plan is part of the cost
A sort or a hash table that fits in memory costs very differently from one that must spill to storage, so the configured memory allowance changes which plans are affordable.
Covering the allowance is necessary because it is a frequent cause of surprise. Raising it can change a plan outright, and the same query can plan differently for two sessions configured differently.
Why the model also costs ordering
A plan producing rows in a useful order can avoid a sort later, so the model tracks what order each node produces and credits plans that exploit it.
Including ordering is necessary because it explains otherwise puzzling choices. An index scan chosen over a cheaper scan is often paying for the order it provides.
Why startup cost is separate from total cost
A plan that produces its first row immediately and a plan that must finish before producing anything are different, and a query that stops early cares which it got.
Covering the split matters because it drives a visible behaviour. A limited query chooses differently from an unlimited one, because only the first rows are being paid for.
Why that explains a common surprise
A query that is fast with a small limit and slow with a larger one has crossed the point where the plan optimised for fast first rows stops winning.
Connecting the mechanism to the symptom makes it recognisable. Nothing has gone wrong, the planner has changed its mind, and the limit is what changed it.
Why the search is as hard as the costing
Even with a perfect model, the number of candidate plans grows very quickly with the number of sources, so the planner must prune rather than enumerate.
Introducing the search problem is necessary because cost and search are separate concerns. A good model with an inadequate search produces bad plans just as reliably.
Why pruning is safe in principle
If a partial plan is already more expensive than a complete one, nothing built on it can win, so whole branches can be discarded without being costed.
Explaining the pruning principle is worthwhile. It is why planning is feasible at all, and it depends on costs being comparable across partial plans.
Why the planner gives up past a threshold
Beyond some number of sources the search is abandoned for a cheaper heuristic, which is a real behaviour change rather than a gradual degradation.
Marking the cliff is important because it is abrupt. A query that acquires one more joined table can get a visibly worse plan, and the reason is that the planner stopped searching.
What to take from the model
Three things. The cost is a ranking device in arbitrary units, not a prediction of duration, so comparing it to a measured time is meaningless. Its two ingredients are configured parameters, which are stable and shift all plans similarly, and estimated counts, which can be wrong by orders of magnitude and decide which plan wins, so almost every bad plan is a bad estimate. And the model deliberately ignores concurrency and momentary memory contents in exchange for plans that stay the same, because a workload whose plans change unpredictably cannot be reasoned about.
Almost every bad plan is a bad estimate rather than a badly configured cost model. The model's parameters are stable and move every plan's cost in the same direction, so being off by a factor of two rarely changes which plan wins. An estimated row count can be wrong by a factor of a thousand, and that changes the winner outright. When a plan is wrong, look at the counts before touching the parameters.