Lenge behandlet jeg databasen som en svart boks. Jeg sendte spørringer inn og fikk data ut, uten å tenke særlig over hva som skjedde imellom. Det fungerte — helt til det ikke gjorde det. En spørring som tok 50ms lokalt tok plutselig 8 sekunder i produksjon med ekte data. Og jeg hadde ingen anelse om hvorfor.
Det var da jeg begynte å lære SQL ordentlig — ikke bare syntaksen, men hva databasen faktisk gjør med spørringene mine.
EXPLAIN ANALYZE: Verktøyet som endret alt
Det viktigste SQL-verktøyet jeg har lært er ikke en spørringsteknikk — det er EXPLAIN ANALYZE. Det viser ikke bare hva PostgreSQL planlegger å gjøre, men hva som faktisk skjer: sequential scans der det burde vært index scans, uventede sort-operasjoner, eller joins som eksploderer i størrelse.
Første gang jeg kjørte EXPLAIN ANALYZE på en treg spørring i Nextbook, så jeg at PostgreSQL scannet hele tabellen for hver rad i en JOIN. En manglende indeks kostet sekunder. Å legge til indeksen tok sekunder. Den opplevelsen lærte meg at EXPLAIN ANALYZE ikke er et avansert verktøy — det er noe du bør bruke som vane.
Indeksering: En del av designet, ikke en ettertanke
Den viktigste leksjonen om indeksering: det er ikke noe du legger til etter at ting er tregt. Det er en del av skjemadesignet.
I Nextbook indekserer jeg kolonner som filtreres på — user_id, status, created_at. For spørringer som filtrerer på flere kolonner samtidig, bruker jeg sammensatte indekser:
CREATE INDEX idx_books_user_status ON books (user_id, status, created_at DESC);Rekkefølgen i en sammensatt indeks betyr noe. PostgreSQL kan bruke indeksen for spørringer som filtrerer på user_id alene, eller user_id + status, men ikke for spørringer som bare filtrerer på status. Det er den typen detaljer en data engineer forklarte meg, og som endret hvordan jeg tenker om databasedesign.
CTEer: Lesbarhet med en kostnad
Common Table Expressions gjør komplekse spørringer lesbare. I stedet for nestede subqueries som ingen forstår tre måneder senere, bryter du opp logikken i navngitte steg:
WITH recent_activity AS (
SELECT book_id, MAX(updated_at) AS last_update
FROM reading_progress
WHERE user_id = $1 AND updated_at > NOW() - INTERVAL '30 days'
GROUP BY book_id
)
SELECT b.id, b.title, b.status, ra.last_update
FROM books b
JOIN recent_activity ra ON ra.book_id = b.id
WHERE b.user_id = $1 AND b.status IN ('reading', 'paused')
ORDER BY ra.last_update DESC
LIMIT 20;Men CTEer er ikke gratis. PostgreSQL materialiserer dem som standard, noe som betyr at de kjøres fullt ut selv om du bare trenger noen rader. For ytelseskritiske spørringer bruker jeg NOT MATERIALIZED for å la planleggeren optimalisere. Det er en trade-off — lesbarhet mot ytelse — og å forstå den avveiningen er viktigere enn å kunne skrive fancy SQL.
Hva SQL har lært meg om systemtenkning
SQL er ikke bare et spørringsspråk for meg — det er et vindu inn i hvordan systemer faktisk fungerer. Å forstå at en indeks er en B-tree, at en JOIN kan implementeres som nested loop eller hash join, at rekkefølgen på betingelser kan påvirke planleggeren — det gir en dypere forståelse av hva som skjer mellom applikasjonen min og dataene.
Jeg er ikke en database-ekspert. Men jeg har lært nok til å forstå hva databasen min gjør, til å designe skjemaer som fungerer, og til å feilsøke trege spørringer. Og den kunnskapen har kommet fra å faktisk se på query plans, fra å spørre folk som kan mer, og fra å innse at databasen ikke er en svart boks — den er et system du kan forstå.