Unit 4.6 · about 4 h
24 / 324Indexes, selectivity and query plans
A missing index turns a lookup into a full scan, and a wrong index taxes writes without helping reads. Plans show what the database actually does, and selectivity decides which design wins on your data.
- explain
- measure
You can now
- Explain B-tree versus sequential access, selectivity, and leftmost-prefix, partial and covering index choices.
- Read
EXPLAINandEXPLAIN ANALYZEplans, compare estimated and actual rows, and name the scan and join strategy. - Measure two index designs on controlled data and explain the selectivity-dependent result with table size and
ANALYZEstate.
Covers
- Indexes, selectivity, cardinality and query plans
Study, in this order
5 steps
- How Indexes WorkAlgoMaster SQL - selected route
How Databases Find Rows Without an Index; B-Tree Indexes (equality, range, sorted output); Hash Indexes; Index Overhead: The Trade-offs; Selectivity: When Indexes Actually Help; Common Mistakes
- Composite, Partial and Covering IndexesAlgoMaster SQL - selected route
Composite Indexes (The Leftmost Prefix Rule, Column Order Guidelines); Partial Indexes; Covering Indexes with
INCLUDE; skimGINandGiSTIndexes - `EXPLAIN` and Execution PlansAlgoMaster SQL - selected route
EXPLAINvsEXPLAIN ANALYZE; Reading a Plan: Cost Fields; Scan Types (sequential, index, index only, bitmap); Join Strategies; Identifying Bottlenecks - Query Optimization and Anti-PatternsAlgoMaster SQL - selected route
Anti-Patterns 1 to 4: Functions on Indexed Columns, Implicit Type Conversions,
SELECT *in Production,ORConditions That Prevent Index Usage; Anti-Pattern 6: The \(N+1\) Query Problem - PostgreSQL 14.1 Using `EXPLAIN`PostgreSQL transactions and EXPLAIN
14.1.1
EXPLAINBasics: the sequential-scan cost as pages timesseq_page_costplus rows timescpu_tuple_cost, pluscpu_operator_costper row for aWHEREcheck; 14.1.2EXPLAIN ANALYZE; 14.1.3 Caveats
Focus on
- High-selectivity equality predicates gain most. Low-selectivity filters and tiny tables stay on sequential scans, and every index taxes writes.
- Equality columns come before range columns in composite indexes, and only the leftmost prefix is usable. A range column first wastes the rest.
EXPLAINshows estimates and planner costs.EXPLAIN ANALYZEexecutes and shows actual rows, loops, buffers and timing. Costs are planner units, not milliseconds.- Plans depend on statistics. State table size and
ANALYZEstate with every claim, and distrust toy-table plans.
Reproduce closed-book
EXPLAIN, add an index on the filter column, and compare EXPLAIN (ANALYZE, BUFFERS) when the matching rows are scattered across the table and when they are clustered.Traps
- Cost numbers are milliseconds. They are abstract planner units; only the
EXPLAIN ANALYZEExecution Timeis measured, on your hardware and cache state. - Wrapping an indexed column in a function. The predicate becomes unsargable and the plan falls back to a scan; rewrite the predicate or index the expression.
- One index per column. Leftmost-prefix rules,
ORconditions and implicit type conversions defeat single-column indexes; check the plan, not the schema.
Practice
Measured plan improvement on a scratch database
14.1.2 EXPLAIN ANALYZE: before and after plans with BUFFERS for a selective filter plus a join from your service schema
Use a scratch PostgreSQL 18 database (a throwaway postgres:18 container works) and a table over 1000 rows, run ANALYZE, and record pages, row counts, both plans, Heap Blocks and both execution times. Fix one anti-pattern from the lesson (function-wrapped column or SELECT star) and show the plan change.
Self-test
-
A 5000-row ANALYZEd table of 32 pages filters
user_id = 7with 100 matches. Where does the sequential scan's cost of 94.50 come from, and what does an index onuser_idchange?32 pages times 1.0 plus 5000 rows times (0.01 + 0.0025) = 94.50. With the matches scattered over every page, the plan becomes a bitmap heap scan (cost 5.06 to 38.31) that still reads all 32 heap pages; with them clustered it is an index scan touching 4 buffers. Measured on PostgreSQL 18.6 with default costs. -
A filter has equality on
statusand a range oncreated_at. How do you order the composite index?Equality first, then range:(status, created_at). Range first wastes the second column under the leftmost rule. -
EXPLAINestimates 10 rows butANALYZEshows 4500 actual. What do you do first?RunANALYZEand re-check. Stale or missing statistics is the first suspect before rewriting the query. -
Why can an index slow a workload down?
Every write maintains each index, and unused or duplicate indexes cost storage and write amplification without speeding any read.
Completion criterion
Stop when
You show before and after EXPLAIN ANALYZE with table size and ANALYZE state, explain why the selective case improved, and name one case on the same tables where the index does not help.
Leads to
- 4.8 HTTP APIs, pagination, streaming and idempotency Index the
idempotency-keyand pagination lookups behind the endpoints. - stage 06 · Systems Foundations Carry plan reading into storage and I/O behavior.
Comes after
- 4.4 Relational modeling, joins, aggregation and window functionsDiagnose the slow joins with plans and selectivity.
- 4.5 Transactions, isolation anomalies and ORM boundariesMeasure the plans behind the invariant checks.
Resources for this unit
- AlgoMaster SQL - selected route Study SQL Foundations, Querying Data, Joins, Subqueries and CTEs, Window Functions, DDL, DML and Views, Performance Optimization, Transactions and Concurrency, and Data Modeling. Stop when the service queries and transaction tests pass.
- PostgreSQL transactions and EXPLAIN Use the transaction tutorial and EXPLAIN guide to demonstrate commit, rollback, one isolation anomaly or prevented anomaly, and a measured plan improvement on the database actually used.
Also in this section
- Official docs typed-service build path Build one record service in this order: structural protocols and generics; Pydantic models, validators, strict mode, serialization and settings; FastAPI request bodies, dependencies, errors and tests; then PostgreSQL transactions and EXPLAIN. Replay valid, malformed, duplicate and previous-schema requests through CLI and HTTP.
- FastAPI tutorial Use Request Body, Dependencies, Handling Errors and Testing to expose the same service layer through HTTP. Keep framework models outside the domain core and preserve the same error semantics as the CLI.
- AlgoMaster Low-Level Design - one rehearsal Use one fitting design prompt to rehearse interface contracts, dependency direction and failure ownership. Treat the review as feedback, then verify behavior with your own tests.
- Stas' Python Cookbook - data and interfaces Use Serialization and Deserialization plus Environment and Arguments for focused implementation help. Compare JSON, JSONL, CSV and compressed data, and never use pickle for untrusted interchange.
- AlgoMaster Python - interfaces, generics and data files Use Interfaces and Protocols plus Generic Types for structural typing, and Working with JSON plus Working with CSV for JSONL, error handling,
newlineandDictReaderbehavior. Lesson quizzes check recall; implementation is verified with your own scripts. - SQLAlchemy 2.1 documentation - select, Session and eager loading Use the
select()tutorial for 2.x query style,Sessionbasics for the transaction boundary and identity map, and relationship loading for lazy versusselectinloadversusjoinedloadplusraiseload. Useselect()style andSession.executeorSession.scalarsonly, never the legacyQueryAPI. - Alembic tutorial - migration scripts and downgrading Use Create a Migration Script, Running our First and Second Migration, Getting Information and Downgrading to run a paired
upgradeanddowngraderound trip on a scratch database. - PyArrow Parquet read and write docs Use Reading and Writing Single Files plus
Tablefrom_pydictandfrom_pylistto convert normalized records to Parquet and test types, nulls, column projection and schema. - Stripe idempotent requests Use the
Idempotency-Keyheader contract, stored-response replay, parameter-mismatch errors andPOST-only scope to implement keyed replay for one endpoint.