CMD Guide
HomeSystem DesignDatabases

SQL Databases

A SQL database stores data as typed rows inside tables and answers declarative queries by feeding them to a cost-based query planner that turns your SELECT into a physical plan over B-tree indexes, while a write-ahead log (WAL) makes every transaction all-or-nothing and crash-durable. You say what you want; the engine decides how to fetch it and guarantees the data never lands in a half-written state.

The four glossary terms are not vocabulary — each is a mechanism that buys a specific guarantee:

Worked example: how a JOIN actually runs

Two tables. Note the FK and the index on the FK column — that index is the whole reason the join is fast.

CREATE TABLE customers (
  id      BIGINT PRIMARY KEY,        -- unique index, auto-created
  email   TEXT NOT NULL UNIQUE,      -- unique index
  country TEXT NOT NULL
);

CREATE TABLE orders (
  id           BIGINT PRIMARY KEY,
  customer_id  BIGINT NOT NULL REFERENCES customers(id),  -- FK
  amount_cents INT    NOT NULL
);

CREATE INDEX idx_orders_customer ON orders(customer_id);
CREATE INDEX idx_customers_country ON customers(country);

Sample data:

customers.idemailcountry
1alice@x.comIN
2bob@x.comUS
3cara@x.comIN
orders.idcustomer_idamount_cents
1011500
1021300
1032900
1043250

The query — total spend per Indian customer:

SELECT c.email, SUM(o.amount_cents) AS total_cents
FROM customers c
JOIN orders o ON o.customer_id = c.id
WHERE c.country = 'IN'
GROUP BY c.email;

What the planner does, step by step (an index nested-loop join):

  1. Filter the small side first. WHERE country = 'IN' hits idx_customers_country, returning ids {1, 3} without scanning bob's row. (With 1M customers, this reads a handful of index pages, not a million rows.)
  2. Probe the FK index once per driving row. For id=1, walk idx_orders_customer to customer_id=1 → orders {101, 102}: amounts 500, 300. For id=3 → order 104: amount 250. Each probe is O(log n) page reads.
  3. Aggregate. GROUP BY email sums each group: alice → 500+300, cara → 250.
emailtotal_cents
alice@x.com800
cara@x.com250

Drop idx_orders_customer and step 2 collapses into a full scan of orders for every driving customer — the join goes from ~O(k·log n) to O(k·n). This is the single most common cause of a slow join in production.

diagram
diagram

Why normalization exists: the update anomaly

Suppose you flatten everything into one table, repeating the customer's country on every order row:

order_idcustomer_emailcountryamount
101alice@x.comIN500
102alice@x.comIN300
104cara@x.comIN250

Alice moves to Singapore. Her country now lives in two rows. Update one and miss the other and the database disagrees with itself — country is both IN and SG for the same person, and no query can tell which is true. Normalizing to separate customers and orders tables stores the country once; the update touches a single row and cannot go half-done. That one-fact-one-place rule is the entire point of PK/FK and normal forms.

What ACID buys you: the WAL trace

Move ₹100 between accounts:

BEGIN;
  UPDATE accounts SET balance = balance - 100 WHERE id = 1;
  UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;

The mechanism behind Atomicity + Durability is the write-ahead log:

  1. Both updates are appended as records to the WAL and the change is made in memory.
  2. COMMIT does not return until the WAL up to this transaction is fsync-ed to disk. Only then is the client told "done."
  3. If the server crashes after the debit but before the credit, restart recovery replays committed WAL records and rolls back the uncommitted transaction. You never observe ₹100 that vanished — the account pair is either fully moved or untouched.

Isolation is the sibling guarantee: while your transaction runs, other sessions don't see your half-applied writes (via locking or MVCC snapshots). Together this is why banks, inventory, and order systems reach for SQL by default.

The four SQL sub-languages, with real statements

SQL is one language split by what it acts on:

GroupActs onExample
DDL — Definitionschema (structure)CREATE TABLE, ALTER TABLE ADD COLUMN, DROP INDEX
DML — Manipulationrows (data)INSERT, UPDATE, DELETE, SELECT
DCL — ControlpermissionsGRANT SELECT ON orders TO analyst, REVOKE
TCL — Transactiontransaction boundariesBEGIN, COMMIT, ROLLBACK, SAVEPOINT

The split matters operationally: DDL often takes stronger locks than DML (an ALTER TABLE can block reads/writes on a hot table), and TCL is what actually triggers the WAL fsync above.

Pitfalls

When to use SQL — and when not to

SQL scales vertically beautifully (one big primary + read replicas) but resists horizontal write scaling: sharding breaks cross-shard joins and multi-row ACID transactions, which are the very things you chose SQL for. That tension drives the decision.

Reach for a relational database (PostgreSQL / MySQL) when:

Prefer an alternative when:

Rule of thumb: choose SQL when correctness and query flexibility dominate and you're not yet forced to shard writes; prefer a NoSQL store when horizontal write scale, schema fluidity, or single-key latency at scale dominate and you can live without joins and strong cross-row transactions. Most systems should start on SQL and only move specific hot paths off it when a measured scaling wall appears — not before.

Takeaways


Re-authored and deepened for this guide. Mechanism and internals draw on Alex Petrov, Database Internals; Martin Kleppmann, Designing Data-Intensive Applications (Ch. 2–3); the PostgreSQL documentation (query planner, WAL, and index chapters); and Markus Winand's Use The Index, Luke! for the indexing and pagination pitfalls. Normalization framing follows C. J. Date, An Introduction to Database Systems.

🤖 Don't fully get this? Learn it with Claude

Stuck on SQL Databases? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **SQL Databases** (System Design) and want to truly understand it. Explain SQL Databases from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **SQL Databases** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **SQL Databases** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **SQL Databases** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes