Skip to content

Query Optimisation

SQLparseASTrewriteLogicalplanoptimisePhysicalplanexecuteResult\mathrm{SQL} \xrightarrow{\mathrm{parse} \mathrm{AST} \xrightarrow{\mathrm{rewrite} \mathrm{Logical} plan \xrightarrow{\mathrm{optimise} \mathrm{Physical} plan \xrightarrow{\mathrm{execute} \mathrm{Result}}}}}

The optimiser estimates the cost of alternative execution plans and chooses the cheapest.

Cost model. Cost = I/O cost (disk page accesses) + CPU cost. For disk-bound queries, I/O Dominates.

Catalog statistics: Table cardinality (nn), attribute value cardinality, number of distinct Values, histogram of value distribution, index information.

Selectivity estimation. For a predicate σA=v(R)\sigma_{A = v}(R)The selectivity is approximately 1/V(A,R)1 / V(A, R) where V(A,R)V(A, R) is the number of distinct values of AA in RR.

Predicate typeSelectivity estimate
A=vA = v1/V(A,R)1 / V(A, R)
A>vA \gt v(max(A)v)/(max(A)min(A))(\max(A) - v) / (\max(A) - \min(A))
A1=v1A2=v2A_1 = v_1 \land A_2 = v_21/V(A1)×1/V(A2)1 / V(A_1) \times 1 / V(A_2)
A1=v1A2=v2A_1 = v_1 \lor A_2 = v_21/V(A1)+1/V(A2)1/(V(A1)×V(A2))1/V(A_1) + 1/V(A_2) - 1/(V(A_1) \times V(A_2))

Nested-loop join. For each tuple in RRScan all of SS.

Cost=nRnSpageaccesses(worstcase)\mathrm{Cost} = n_R \cdot n_S \mathrm{ page} accesses (worst case)

If one relation fits in memory, buffer it and scan the other: cost = nR+nSn_R + n_S.

Block nested-loop join. Use BB buffer pages. Load blocks of RR into B2B - 2 buffers, scan SS With the remaining buffer.

Cost=nR+nR/(B2)nS\mathrm{Cost} = n_R + \lceil n_R / (B - 2) \rceil \cdot n_S

Sort-merge join. Sort both relations on the join attribute, then merge.

Cost=2nRlogB1(nR)+2nSlogB1(nS)+nR+nS\mathrm{Cost} = 2 \cdot n_R \cdot \log_{B-1}(n_R) + 2 \cdot n_S \cdot \log_{B-1}(n_S) + n_R + n_S

Efficient for large relations, especially when both are already sorted.

Hash join. Build a hash table on the smaller relation (build phase), then probe with the larger (probe phase).

Cost=3(nR+nS)(ifbuildrelationfitsinmemory)\mathrm{Cost} = 3 \cdot (n_R + n_S) \mathrm{ (if build relation fits in memory)}

Best for equi-joins when one relation fits in memory.

Index nested-loop join. For each tuple in RRUse an index on SS to find matching tuples.

Cost=nR(indexlookupcost)\mathrm{Cost} = n_R \cdot (\mathrm{index} lookup cost)

Efficient if SS has an index on the join attribute and nRn_R is small.

The optimiser explores the space of equivalent logical plans and physical implementations. For kk Joins, the number of join orderings is O(k!)O(k!) (left-deep trees) or O(3k)O(3^k) (bushy trees). Practical optimisers use dynamic programming with pruning.

Heuristic transformations:

  • Push selections down (reduce intermediate result sizes).
  • Push projections down (reduce column widths).
  • Convert cross products to joins when possible.
  • Reorder joins based on estimated cardinalities.

7.5 Key Relationships Between Join Algorithms

Section titled “7.5 Key Relationships Between Join Algorithms”
AlgorithmBest use caseCost (pages)Memory required
Nested-loopSmall nRn_R (outer small)nRnSn_R \cdot n_SMinimal
Block nested-loopMedium-sized tablesnR+nR/(B2)nSn_R + \lceil n_R/(B-2)\rceil n_SBB pages
Sort-mergeLarge tables, sorted input2nRlognR+2nSlognS+nR+nS2n_R\log n_R + 2n_S\log n_S + n_R + n_SBB pages
HashEqui-join, one relation fits3(nR+nS)3(n_R + n_S)Build table
Index nested-loopSmall outer, indexed innernR(index cost)n_R \cdot \text{(index cost)}Minimal
  • Assuming the cheapest plan for one query is best for all. The optimal join order depends critically on selectivity estimates. Outdated statistics produce poor plans.
  • Forgetting that selectivity estimates are just estimates. Uniform distribution assumptions are often wrong. Histograms and sampling improve accuracy but never guarantee correctness.
  • Confusing left-deep and bushy trees for the same join. Left-deep trees pipeline well but may miss optimal orderings. Bushy trees can exploit more parallelism.
  • Thinking index nested-loop join always beats full table scan. If the outer relation is large and the index has poor selectivity (many matching tuples per key), scanning may be cheaper.

Problem. Consider RR with 1000 pages and SS with 500 pages, 100 buffer pages (B=100B = 100). Compare the cost of block nested-loop join vs sort-merge join.

Solution. Block nested-loop: Cost=1000+1000/(1002)500=1000+11500=6500\mathrm{Cost} = 1000 + \lceil 1000/(100-2)\rceil \cdot 500 = 1000 + 11 \cdot 500 = 6500 pages.

Sort-merge: sorting RR costs 21000log99(1000)210002=40002 \cdot 1000 \cdot \log_{99}(1000) \approx 2 \cdot 1000 \cdot 2 = 4000. Sorting SS: 2500log99(500)25002=20002 \cdot 500 \cdot \log_{99}(500) \approx 2 \cdot 500 \cdot 2 = 2000. Merge: 1000+500=15001000 + 500 = 1500. Total: 4000+2000+1500=75004000 + 2000 + 1500 = 7500 pages.

Block nested-loop is cheaper in this case (6500 vs 7500). \blacksquare

Problem. Estimate selectivity for σA>100B=5(R)\sigma_{A > 100 \land B = 5}(R) given V(A,R)=50V(A,R) = 50, min(A)=0\min(A)=0, max(A)=200\max(A)=200, V(B,R)=20V(B,R) = 20.

Solution. For A>100A > 100: selectivity (200100)/(2000)=0.5\approx (200-100)/(200-0) = 0.5. For B=5B = 5: selectivity 1/20=0.05\approx 1/20 = 0.05. Assuming independence: combined selectivity =0.5×0.05=0.025= 0.5 \times 0.05 = 0.025 (2.5% of rows). \blacksquare

  • Big data systems: Query optimisers in Spark SQL, Hive, and Presto use cost-based optimisation adapted for distributed execution, factoring in network transfer costs.
  • NoSQL databases: Document stores like MongoDB have query optimisers that select between collection scans and index usage, with query planners showing execution statistics.
  • Data warehousing: Columnar databases (Snowflake, Redshift) use optimisers that account for column pruning, vectorised execution, and zone maps for min-max pruning.
  • Stream processing: Optimisers for streaming SQL (Flink, Kafka Streams) extend cost models to handle windowed aggregations, state size, and watermark propagation.

7.9 Summary Table of Optimisation Techniques

Section titled “7.9 Summary Table of Optimisation Techniques”
TechniqueWhen to applyBenefit
Predicate pushdownFilter after scanReduces rows early
Projection pushdownWide tables with few columns neededReduces I/O per row
Join reorderingMultiple joins with selective filtersMinimises intermediate size
Index-only scanCovered queryAvoids table access
Materialised viewExpensive aggregationsPrecomputes results