Database Cheat Sheet
“Problem says X → think Y.” One line per need; click a row to open the lesson.
Indexing
Fast lookup by a column->B-tree indexFast lookup by two columns together->Composite index (equality col first)Filter on a rare value of a low-cardinality column->Partial index WHERE that valueCase-insensitive / functional lookup->Expression index on lower(col)Avoid touching the table at all->Covering index (INCLUDE the selected columns)Search inside arrays / JSONB / full text->GIN indexORDER BY … LIMIT is slow->Index that provides the sort orderQuery ignores the index->Check sargability, leftmost prefix, selectivity, statisticsThe FK-child query is slow->Index the referencing column (not auto-created)
Modeling
A relationship between two entities->Foreign key on the many sideExactly one child per parent->UNIQUE foreign keyMany-to-many->Junction table with the pair’s attributesA hierarchy / tree->Self-referencing parent_id; recursive CTE to walk itAn identifier that might change->Surrogate PK + UNIQUE on the natural keyRepeating columns (product_1, product_2)->Child table, one row each (1NF)Same fact stored in many rows->Normalize to 3NFA read that joins six tables on every view->Denormalize a maintained copyThe price paid must not change with the catalogue->Snapshot column (history, not redundancy)Prevent overlapping ranges (bookings)->EXCLUDE constraint (GiST)Variable-shape data->JSONB column, GIN-indexed
SQL
Filter groups, not rows->HAVING (WHERE runs before aggregation)Find duplicates->GROUP BY … HAVING count(*) > 1Anti-join (rows with no match)->NOT EXISTS (NULL-safe), not NOT INRank / running total / previous value->Window functionTop N per group->row_number() OVER (PARTITION BY …) then WHERE rn <= NPercentage of a group total per row->sum(x) OVER (PARTITION BY g) / valueTest a column for NULL->IS NULL / IS NOT NULL (never = NULL)sum() returns NULL on empty groups->coalesce(sum(x), 0)Deep pagination is slow->Keyset pagination (WHERE id > :last), not OFFSETWalk a graph or tree in SQL->Recursive CTE (with a depth guard)
Performance
Diagnose a slow query->EXPLAIN (ANALYZE, BUFFERS)One slow page, all queries fast->Look for N+1 (query count per request)SUM over a join is too high->Aggregate the child before joining (fan-out)A plan went bad after a bulk load->ANALYZE (stale statistics)SELECT * on a wide table is slow->Select only needed columns (enables index-only)Sort spills to disk->Index the sort key, or raise work_mem
Transactions
Several statements must succeed together->One transaction (BEGIN … COMMIT)Concurrent counter increments lose updates->SET x = x + 1, or FOR UPDATE, or version checkRead the same data twice, consistently->REPEATABLE READAct on a condition you checked but did not write->SERIALIZABLE + retry, or a constraintRead a row you are about to update->SELECT … FOR UPDATEA job queue in the database->FOR UPDATE SKIP LOCKEDAvoid deadlocks->Lock rows in a consistent order; keep txns shortA table bloats though rows are flat->Find the long transaction blocking VACUUM
Scaling
Scale reads->Read replicas (mind replication lag)Scale writes past one machine->Sharding (last resort)A huge single table is hard to operate->Partition by time (still one machine)User sees their own write revert->Read-your-writes routing / wait for LSN"Too many connections" at idle CPU->Connection pooler (PgBouncer)Choose CP or AP->Decide what happens during a partitionStrong reads across replicas->Quorum W + R > N
Caching
Repeated expensive read->Cache-aside with a TTLA deploy stampedes the database->TTL jitter / single-flight / stale-while-revalidateOne key is most of the traffic->Replicate the hot key (L1 cache)Cache stale after a direct SQL edit->Change data capture invalidationLeaderboard / rank at scale->Redis sorted setRate limiting->Redis INCR+EXPIRE or sorted-set sliding window
NoSQL
Semantic / similarity search->Vector index (pgvector HNSW)Exact-token + semantic search->Hybrid: BM25 + vectorRelevance-ranked text search->Full-text (tsvector + GIN), then a search engineMulti-hop traversal queries->Graph databaseVery high-volume time-ordered writes->Wide-column or time-series storeWhole-object reads with variable shape->Document store (embed vs reference)
Internals
Why does the engine read 8 KB to fetch one 100-byte row?->The page is the unit of I/O and cachingA record grew on UPDATE and the row "moved"->Slotted page: slots stay, offsets change; overflow to another pageHow does an index find a row in 3 reads among millions?->B+ tree: fanout ~200, height 3–4, linked leavesWhy not a balanced binary tree for the index?->Fanout: 24 random page reads vs 3Index on = only, never ranges->Hash index: bucket lookup, no orderingSecond run of the query is 100× faster->Buffer pool hit vs missA big scan evicted the working set->Replacement policy: Clock, ring buffers, old/young listsWhat does COMMIT wait for?->WAL fsync — not the data pagesCrash after COMMIT — is the data safe?->Yes: recovery replays the WAL from the last checkpointTwo transactions want the same row->Lock manager: S/X compatibility, wait queueDeadlock detected, transaction aborted->Cycle in the waits-for graph; one victim chosenReaders never block writers — how?->MVCC: version chains + snapshot visibility ruleTable keeps growing after DELETE->Dead tuples until VACUUM; long snapshots pin themSerialization failure, please retry->SSI detected a rw-dependency cycleMillions of writes per second, mostly inserts->LSM tree: WAL → memtable → SSTable → compactionPoint read checks many files->Bloom filter per SSTable skips the definite missesDisk usage 3× the data on an LSM store->Space amplification; compaction backlogB+ tree or LSM for this workload?->Reads and ranges → B+; write-heavy ingest → LSMPlanner picked Seq Scan although an index exists->Cost model: selectivity × random page cost > sequentialWhich join algorithm will it use?->Nested loop (small/indexed), hash (equality, fits memory), merge (sorted inputs)Why is the InnoDB secondary lookup two descents?->Secondary index → PK → clustered indexReplica shows old data->Async replication lag: LSN on replica < primaryAdding a node moved 80% of keys->Consistent hashing / virtual nodes instead of moduloPrimary died — who is leader now?->Election with a majority quorum; fence the old primarySlow, and EXPLAIN is not enough->Count pages, buffer misses, sorts, WAL, contention