KoderKoder.ai
PrijzenEnterpriseOnderwijsVoor investeerders
InloggenAan de slag

Product

PrijzenEnterpriseVoor investeerders

Bronnen

Neem contact opOndersteuningOnderwijsBlog

Juridisch

PrivacybeleidGebruiksvoorwaardenBeveiligingBeleid voor acceptabel gebruikMisbruik melden

Sociaal

LinkedInTwitter
Koder.ai
Taal

© 2026 Koder.ai. Alle rechten voorbehouden.

Home›Blog›Jeffrey Ullmans databasetheorie achter snelle, schaalbare queries
04 mei 2025·8 min

Jeffrey Ullmans databasetheorie achter snelle, schaalbare queries

Hoe Jeffrey Ullmans kernideeën moderne databases aandrijven: relationele algebra, optimalisatieregels, joins en compiler-achtige planning die systemen helpt schalen.

Jeffrey Ullmans databasetheorie achter snelle, schaalbare queries

Waarom Ullman belangrijk is voor moderne data

De meeste mensen die SQL schrijven, dashboards maken of een trage query tunen hebben baat gehad bij het werk van Jeffrey Ullman — zelfs als ze zijn naam nooit gehoord hebben. Ullman is een computerwetenschapper en docent wiens onderzoek en studieboeken hebben bepaald hoe databases data beschrijven, over queries redeneren en ze efficiënt uitvoeren.

De stille invloed achter dagelijkse tools

Wanneer een database-engine je SQL omzet naar iets dat snel draait, leunt die op ideeën die zowel precies als aanpasbaar moeten zijn. Ullman hielp de betekenis van queries te formaliseren (zodat het systeem ze veilig kan herschrijven) en bracht database-denken in verband met compiler-denken (zodat een query geparsed, geoptimaliseerd en vertaald kan worden naar uitvoerbare stappen).

Die invloed is stil omdat ze niet verschijnt als een knop in je BI-tool of als een zichtbare functie in je cloudconsole. Ze verschijnt als:

  • queries die snel lopen nadat je een index toevoegt of een JOIN herschrijft
  • optimizers die andere plannen kiezen naarmate data groeit
  • systemen die kunnen schalen zonder dat het resultaat van je query verandert

Wat je in dit artikel leert (zonder wiskundige overvloed)

Dit artikel gebruikt Ullmans kernideeën als een rondleiding door database-interne onderdelen die in de praktijk het meest belangrijk zijn: hoe relationele algebra onder SQL ligt, hoe query-herschrijvingen betekenis behouden, waarom kosten-gebaseerde optimizers de keuzes maken die ze maken, en hoe join-algoritmen vaak bepalen of een taak in seconden of uren klaar is.

We halen ook een paar compiler-achtige concepten binnen — parsen, herschrijven en plannen — omdat database-engines meer op geavanceerde compilers lijken dan veel mensen denken.

Een korte belofte: we houden de discussie nauwkeurig, maar vermijden wiskundige bewijzen. Het doel is mentale modellen te geven die je de volgende keer dat prestaties, schaal of verwarrend query-gedrag opduikt op het werk kunt toepassen.

Databasefundamenten die Ullman heeft helpen verankeren

Als je ooit een SQL-query hebt geschreven en verwacht dat die "gewoon één ding betekent", vertrouw je op ideeën die Jeffrey Ullman hielp populariseren en formaliseren: een helder model voor data, plus precieze manieren om te beschrijven wat een query vraagt.

Het relationele model in gewone taal

In kern beschouwt het relationele model data als tabellen (relaties). Elke tabel heeft rijen (tuples) en kolommen (attributen). Dat klinkt nu vanzelfsprekend, maar het belangrijkste is de discipline die het creëert:

  • Sleutels identificeren rijen. Een primaire sleutel is het ‘naamplaatje’ voor elk record.
  • Relaties verbinden tabellen via foreign keys, zodat feiten op één plek gehouden worden en elders kunnen worden gerefereerd.

Deze manier van kijken maakt het mogelijk om over correctheid en prestaties te redeneren zonder te sussen. Als je weet wat een tabel vertegenwoordigt en hoe rijen geïdentificeerd worden, kun je voorspellen wat joins zouden moeten doen, wat duplicaten betekenen en waarom bepaalde filters resultaten veranderen.

Relationele algebra: een rekenmachine voor queries

Ullmans lesmateriaal gebruikt vaak relationele algebra als een soort query-calculator: een kleine set bewerkingen (selectie, projectie, join, unie, verschil) die je kunt combineren om uit te drukken wat je wilt.

Waarom het uitmaakt voor werken met SQL: databases vertalen SQL naar een algebraïsche vorm en herschrijven die vervolgens naar een equivalente vorm. Twee queries die er anders uitzien kunnen algebraïsch hetzelfde zijn — en zo kunnen optimizers joins herschikken, filters naar beneden duwen of onnodig werk verwijderen terwijl de betekenis blijft.

Algebra vs. calculus (op hoofdlijnen)

  • Relationele algebra is meer “hoe”: een reeks bewerkingen om het resultaat te berekenen.
  • Relationele calculus is meer “wat”: een beschrijving van het resultaat dat je wil.

SQL is grotendeels “wat”, maar engines optimaliseren vaak met algebraïsche “hoe”-vormen.

Fundamenten boven het uit het hoofd leren van een dialect

SQL-dialecten verschillen (Postgres vs. Snowflake vs. MySQL), maar de fundamenten niet. Begrijpen van sleutels, relaties en algebraïsche equivalentie helpt je zien wanneer een query logisch fout is, wanneer ze alleen traag is en welke wijzigingen de betekenis behouden over platforms heen.

Relationele algebra: de verborgen taal onder SQL

Relationele algebra is de “wiskunde onder” SQL: een kleine set operatoren die beschrijven welk resultaat je wilt. Jeffrey Ullmans werk maakte deze operator-werkwijze scherp en leerbaar — en het is nog steeds het mentale model dat de meeste optimizers gebruiken.

De kernoperatoren (en wat ze betekenen)

Een databasequery kan worden uitgedrukt als een pijplijn van een paar bouwstenen:

  • Select (σ): filter rijen (het SQL-idee van WHERE)
  • Project (π): houd specifieke kolommen (het SQL-idee van SELECT col1, col2)
  • Join (⋈): combineer tabellen op basis van een conditie (JOIN ... ON ...)
  • Union (∪): stapel resultaten met dezelfde vorm (UNION)
  • Difference (−): rijen in A maar niet in B (zoals EXCEPT in veel SQL-dialecten)

Omdat de set klein is, wordt het eenvoudiger om over correctheid te redeneren: als twee algebra-expressies equivalent zijn, geven ze voor elke geldige databasetoestand dezelfde tabel terug.

Hoe SQL conceptueel naar algebra mappt

Neem een bekende query:

SELECT c.name
FROM customers c
JOIN orders o ON o.customer_id = c.id
WHERE o.total > 100;

Conceptueel is dit:

  1. begin met een join van customers en orders: customers ⋈ orders

  2. selecteer alleen orders boven 100: σ(o.total > 100)(...)

  3. projecteer de ene kolom die je wilt: π(c.name)(...)

Dat is niet de exacte interne notatie die elke engine gebruikt, maar het is het juiste idee: SQL wordt een operatorboom.

Equivalentie: de toegang tot optimalisatie

Verschillende bomen kunnen hetzelfde betekenen. Filters kunnen vaak eerder worden toegepast (pas σ toe vóór een grote join), en projecties kunnen ongebruikte kolommen eerder weggooien (pas π eerder toe).

Die equivalentieregels laten een database je query herschrijven naar een goedkoper plan zonder de betekenis te veranderen. Zodra je queries als algebra ziet, stopt “optimalisatie” met magie en wordt het een veilige, op regels gebaseerde herstructurering.

Van SQL naar queryplannen: herschrijvingen die betekenis behouden

Als je SQL schrijft, voert de database het niet “zoals geschreven” uit. Ze zet je statement om in een queryplan: een gestructureerde representatie van het werk dat gedaan moet worden.

Een goed mentaal model is een boom van operatoren. Bladeren lezen tabellen of indexen; interne knopen transformeren en combineren rijen. Veelvoorkomende operatoren zijn scan, filter (selectie), project (kolomkeuze), join, group/aggregate en sort.

Logisch plan vs. fysiek plan (wat vs. hoe)

Databases scheiden planning vaak in twee lagen:

  • Logisch plan: wat resultaat berekend moet worden, uitgedrukt met abstracte operatoren (filter, join, aggregate) en relaties daartussen.
  • Fysiek plan: hoe dat uitgevoerd wordt op echte opslag en hardware (indexscan vs volledige scan, hash join vs nested-loop join, parallel vs single-threaded).

Ullmans invloed zie je in de nadruk op betekenisbehoudende transformaties: herschik het logische plan op veel manieren zonder het antwoord te veranderen, en kies daarna een efficiënte fysieke strategie.

Regels voor herschrijvingen die werk verminderen

Voordat de engine de uiteindelijke uitvoeringsaanpak kiest, past de optimizer algebraïsche “schoonmaak”-regels toe. Deze herschrijvingen veranderen het resultaat niet; ze verminderen onnodig werk.

Veelvoorkomende voorbeelden:

  • Selection pushdown: filters zo vroeg mogelijk toepassen zodat er minder rijen door latere stappen stromen.
  • Projection pruning: alleen de benodigde kolommen houden, wat I/O en geheugen verkleint.
  • Join reordering: kleinere/intermediaire resultaten eerst joinen (wanneer veilig), in plaats van de oppervlakkige volgorde uit SQL te volgen.

Een simpel rewrite-voorbeeld

Stel dat je orders wilt voor gebruikers in één land:

SELECT o.order_id, o.total
FROM users u
JOIN orders o ON o.user_id = u.id
WHERE u.country = 'CA';

Een naïeve interpretatie zou alle gebruikers joinen met alle orders en daarna filteren op Canada. Een betekenisbehoudende herschrijving duwt de filter naar beneden zodat de join minder rijen raakt:

  • Filter users naar country = 'CA'
  • Join vervolgens die gebruikers met orders
  • Projecteer dan alleen order_id en total

In planten termen probeert de optimizer dit te veranderen van:

Join(Users, Orders) → Filter(country='CA') → Project(order_id,total)

naar iets als:

Filter(country='CA') on Users → Join(with Orders) → Project(order_id,total)

Zelfde antwoord. Minder werk.

Deze herschrijvingen zijn makkelijk over het hoofd te zien omdat je ze nooit intypt—maar ze zijn een hoofdreden dat dezelfde SQL snel op de ene database kan draaien en traag op een andere.

Kosten-gebaseerde optimalisatie zonder jargon

Als je een SQL-query uitvoert, overweegt de database meerdere geldige manieren om hetzelfde antwoord te krijgen en kiest vervolgens degene die ze het goedkoopst verwacht. Dat beslissingsproces heet kosten-gebaseerde optimalisatie—en het is één van de meest praktische plekken waar Ullman-achtige theorie dagelijks terugkomt.

Wat een “kostmodel” echt is

Een kostmodel is een scoresysteem dat de optimizer gebruikt om alternatieve plannen te vergelijken. De meeste engines schatten kosten met een paar kernbronnen:

  • Verwerkte rijen (werk schaalt vaak met hoeveel data door elke stap stroomt)
  • I/O (pagina's van disk of SSD lezen, plus cache-effecten)
  • CPU (filteren, hash-en, sorteren, aggregeren)
  • Geheugen (past een operatie in RAM of spoelt die naar schijf)

Het model hoeft niet perfect te zijn; het moet vaak genoeg richtinggevend juist zijn om goede plannen te kiezen.

Cardinaliteitschatting, in gewone taal

Voordat het plannen kan scoren, stelt de optimizer bij elke stap de vraag: hoeveel rijen zal dit opleveren? Dat is cardinality estimation.

Als je filtert met WHERE country = 'CA', schat de engine welk deel van de tabel matcht. Als je klanten en orders joinet, schat ze hoeveel paren er op de join-sleutel matchen. Die rijaantal-gissingen bepalen of ze een indexscan verkiest boven een volledige scan, een hash join boven een nested loop, of dat een sort klein of enorm zal zijn.

Waarom statistieken er toe doen (en wat misgaat zonder)

De schattingen van de optimizer worden aangedreven door statistieken: aantallen, waardeverdelingen, null-percentages en soms correlaties tussen kolommen.

Als statistieken verouderd of afwezig zijn, kan de engine rijaantallen met veelvoud fout inschatten. Een plan dat goedkoop lijkt op papier kan in de praktijk duur blijken—klassieke symptomen zijn plotselinge vertragingen na datagroei, “willekeurige” planwisselingen of joins die onverwacht naar schijf moeten uitwijken.

De onvermijdelijke trade-off: nauwkeurigheid vs. plantijd

Betere schattingen vereisen vaak meer werk: gedetailleerdere stats, sampling of het verkennen van meer kandidaat-plannen. Maar plannen kost zelf ook tijd, vooral voor complexe queries.

Dus optimizers balanceren twee doelen:

  • Plan snel genoeg voor interactieve workloads
  • Plan slim genoeg om catastrofale keuzes te vermijden

Dat inzicht helpt je EXPLAIN-output te interpreteren: de optimizer probeert niet sluw te zijn—hij probeert onder beperkte informatie voorspelbaar juist te zijn.

Join-algoritmen en het hart van query-prestaties

Test join strategies quickly
Prototype join-heavy pages fast and refine performance without rewriting everything by hand.
Create Project

Ullmans werk hielp een eenvoudig maar krachtig idee populair maken: SQL wordt niet zozeer “gedraaid” als wel vertaald naar een uitvoeringsplan. Nergens is dat duidelijker dan bij joins. Twee queries die dezelfde rijen teruggeven kunnen enorm verschillen in uitvoeringstijd afhankelijk van welk join-algoritme de engine kiest — en in welke volgorde tabellen worden gejoined.

Nested loop, hash join, merge join — wanneer welke geschikt is

Nested loop join is conceptueel eenvoudig: voor elke rij aan de linkerkant zoek je bijpassende rijen aan de rechterkant. Het kan snel zijn wanneer de linkerkant klein is en de rechterkant een bruikbare index heeft.

Hash join bouwt een hashtabel van één input (vaak de kleinere) en proeft die met de andere. Het blinkt uit bij grote, ongesorteerde inputs met gelijkheidscondities (bijv. A.id = B.id), maar heeft geheugen nodig; uitwisseling naar schijf kan het voordeel tenietdoen.

Merge join loopt twee inputs af in sorteervolgorde. Het is een goede keuze wanneer beide kanten al gesorteerd zijn (of goedkoop te sorteren), zoals wanneer indexen rijen in join-key volgorde leveren.

Waarom join-volgorde dominante invloed kan hebben

Bij drie of meer tabellen explodeert het aantal mogelijke join-volgorden. Twee grote tabellen eerst joinen kan een enorm tussenresultaat opleveren dat alles vertraagt. Een betere volgorde begint vaak met het meest selectieve filter (weinigste rijen) en joinet naar buiten toe, zodat tussenresultaten klein blijven.

Indexen veranderen het menu van goede plannen

Indexen versnellen niet alleen opzoekingen — ze maken bepaalde joinstrategieën mogelijk. Een index op de join-sleutel kan een dure nested loop veranderen in een snelle “seek per rij”-patroon. Ontbrekende of onbruikbare indexen kunnen de engine dwingen tot hash joins of grote sorts voor merge joins.

Praktische checklist: signalen van een slecht join-plan

  • Looptijd groeit dramatisch bij een beetje meer data (join-volgorde vergroot tussenresultaten).
  • Het plan toont enorme verschillen "geschat vs werkelijkheid" in rijen (slechte cardinaliteitsschattingen leiden tot verkeerde join-keuzes).
  • Je ziet grote sorts of hash-spills naar schijf (geheugendruk of ontbrekende ondersteunende indexen).
  • Een kleine gefilterde tabel wordt laat gejoined in plaats van vroeg (filters worden niet vroeg toegepast).
  • De join-conditie is geen nette gelijkheid op compatibele types (voorkomt efficiënte hash/merge gedrag).

Compilerideeën binnen database-engines

Databases "runnen" SQL niet zomaar. Ze compileren het. Ullmans invloed bestrijkt zowel databasetheorie als compiler-denken, en die verbinding verklaart waarom query-engines zich gedragen als toolchains voor programmeertalen: ze vertalen, herschrijven en optimaliseren voordat er werk wordt gedaan.

Parsen en syntaxisbomen: hoe SQL gelezen wordt

Als je een query verstuurt, lijkt de eerste stap op de front-end van een compiler. De engine tokeniseert keywords en identifiers, controleert grammatica en bouwt een parse tree (vaak vereenvoudigd naar een abstract syntax tree). Hier worden basisfouten opgevangen: ontbrekende komma's, dubbelzinnige kolomnamen, ongeldige GROUP BY-regels.

Een nuttig mentaal model: SQL is een programmeertaal waarvan het “programma” toevallig datarelaties beschrijft in plaats van lussen.

Van parse tree naar logische operatoren

Compilers zetten syntaxis om naar een tussenrepresentatie (IR). Dat doen databases ook: ze vertalen SQL-syntaxis naar logische operatoren zoals:

  • Selectie (rijen filteren)
  • Projectie (kolommen kiezen)
  • Join (tabellen combineren)
  • Aggregatie (GROUP BY)

Die logische vorm ligt dichter bij relationele algebra dan bij SQL-tekst, wat het makkelijker maakt om over betekenis en equivalentie te redeneren.

Waarom optimizers lijken op compileroptimalisaties

Compileroptimalisaties houden programmaresultaten identiek terwijl uitvoer goedkoper wordt. Database-optimizers doen hetzelfde met regelsystemen zoals:

  • filters eerder toepassen (minder werk eerder)
  • joins herordenen (zelfde resultaat, andere kosten)
  • redundante berekeningen verwijderen

Dat is de databasevariant van “dead code elimination”: niet identieke technieken, maar dezelfde filosofie — behoud semantiek, verlaag kosten.

Debuggen: plannen lezen als gecompileerde code

Als je query traag is, kijk dan niet alleen naar SQL. Bekijk het queryplan zoals je compiler-output zou inspecteren. Een plan vertelt je wat de engine daadwerkelijk koos: join-volgorde, indexgebruik en waar tijd wordt besteed.

Praktische conclusie: leer EXPLAIN-output lezen als een prestatie-"assembly listing". Het maakt tunen van gokken naar evidence-based debugging. Voor meer over hoe je dat een gewoonte maakt: zie practical-query-optimization-habits.

Schema-ontwerptheorie die echte prestaties raakt

See performance in production
Deploy your app and catch slow queries under realistic traffic patterns sooner.
Deploy Now

Goede query-prestaties beginnen vaak voordat je SQL schrijft. Ullmans schema-ontwerptheorie (vooral normalisatie) gaat over data zó structureren dat de database correct, voorspelbaar en efficiënt blijft naarmate het groeit.

Doelen van normalisatie (waarom het bestaat)

Normalisatie streeft naar:

  • Anomalieën verminderen (bijv. een klantadres op vijf plekken bijwerken en eentje vergeten)
  • Consistentie verbeteren door elk feit één "thuis" te geven
  • Constraints uitdrukbaar maken (sleutels, foreign keys) zodat de engine regels kan afdwingen in plaats van applicatiecode

Die correctheidswinst vertaalt zich later in prestatievoordelen: minder gedupliceerde velden, kleinere indexen en minder dure updates.

Normalisatievormen in gewone taal

Je hoeft geen bewijzen te onthouden om de ideeën te gebruiken:

  • 1NF: waarden in atomische kolommen (geen komma-gescheiden lijsten). Dit maakt filteren en indexeren eenvoudig.
  • 2NF: in tabellen met een samengestelde sleutel moet elke niet-sleutelkolom afhankelijk zijn van de héle sleutel (niet slechts een deel). Dit voorkomt herhaalde attributen.
  • 3NF: niet-sleutelkolommen mogen alleen van de sleutel afhangen, niet van andere niet-sleutelkolommen. Dit voorkomt verborgen duplicatie.
  • BCNF: een strengere versie van 3NF waarbij elke determinant een kandidaat-sleutel is — nuttig bij “bijna unieke” kolommen die subtiele duplicaten veroorzaken.

Wanneer denormalisatie redelijk is

Denormalisatie kan verstandig zijn wanneer:

  • je analytics-zware tabellen bouwt (bredere fact-tables voor rapportage)
  • joins de bottleneck worden en je gecontroleerde redundantie accepteert
  • je optimaliseert voor reads met duidelijke verversregels (bv. nightly rebuilds)

Het belangrijkste is doelbewust denormaliseren, met een proces om duplicaten synchroon te houden.

Hoe schema-keuzes optimizer en schaal beïnvloeden

Schema-ontwerp bepaalt wat de optimizer kan doen. Duidelijke sleutels en foreign keys maken betere joinstrategieën, veiligere herschrijvingen en nauwkeurigere rijaantal-schattingen mogelijk. Tegelijkertijd kan overmatige duplicatie indexen opblazen en writes vertragen, en multi-waarde kolommen blokkeren efficiënte predicaten. Naarmate data groeit, blijken deze vroege modelkeuzes vaak belangrijker dan micro-optimalisaties van één enkele query.

Hoe theorie zichtbaar wordt als systemen schalen

Wanneer een systeem "schaalt", gaat het zelden alleen om grotere machines. Vaak is het lastige dat dezelfde querybetekenis bewaard moet blijven terwijl de engine een heel andere fysieke strategie kiest om runtimes voorspelbaar te houden. Ullmans nadruk op formele equivalenties is precies wat die strategiewijzigingen mogelijk maakt zonder resultaten te veranderen.

Schaal is vaak fysieke lay-out + plankeuze

Bij kleine datasets werken veel plannen. Bij schaal kan het verschil tussen een tabel scannen, een index gebruiken of een voorgecomputeerd resultaat gebruiken het verschil betekenen tussen seconden en uren. De theoretische kant is belangrijk omdat de optimizer een veilige set herschrijvingsregels nodig heeft (bijv. filters naar voren duwen, joins herordenen) die het antwoord niet veranderen — zelfs als ze het werk radicaal veranderen.

Partitionering verandert de query die je draait, ook al ziet SQL er hetzelfde uit

Partitionering (op datum, klant, regio, enz.) verandert één logische tabel in veel fysieke stukken. Dat beïnvloedt planning:

  • welke partitities overgeslagen kunnen worden (partition pruning)
  • of joins binnen partitities gebeuren of dat rijen over nodes geshuffled moeten worden
  • of aggregeren lokaal kan gebeuren voordat resultaten gecombineerd worden

De SQL-tekst kan ongewijzigd blijven, maar het beste plan hangt nu af van waar de rijen fysiek liggen.

Materialized views: precomputation als algebraïsche snelkoppelingen

Materialized views zijn in wezen “opgeslagen subexpressies.” Als de engine kan bewijzen dat je query overeenkomt met (of herschreven kan worden naar) een opgeslagen resultaat, kan hij duur werk vervangen door een snelle lookup. Dit is relationele algebra in de praktijk: equivalente expressies herkennen en dan hergebruiken.

Caching: nuttig, maar kan de verkeerde werkvorm niet herstellen

Caching versnelt herhaalde reads, maar lost geen query op die te veel data moet scannen, enorme tussenresultaten moet shufflen of een gigantische join moet berekenen. Bij schaalproblemen is de oplossing vaak: reduceer de hoeveelheid data die geraakt wordt (lay-out/partitionering), verminder herhaald werk (materialized views) of verander het plan — niet alleen “meer cache.”

Praktische optimalisatiegewoonten geïnspireerd door Ullman

Ullmans invloed zie je in één praktische ingesteldheid: behandel een trage query als een intentieverklaring die de database vrij is te herschrijven, en verifieer vervolgens wat hij daadwerkelijk heeft besloten te doen. Je hoeft geen theoreticus te zijn om te profiteren — je hebt alleen een herhaalbare routine nodig.

1) Lees een EXPLAIN-plan: waar eerst op te letten

Begin met de onderdelen die meestal runtime domineren:

  • Toegangswijze: scant de engine een hele tabel terwijl je een indexlookup verwachtte?
  • Rij-schattingen vs. werkelijke aantallen (als je database die toont): grote gaps verklaren vaak mysterieuze traagheid.
  • Join-volgorde: welke tabel drijft de join en begint het met de meest selectieve filter?
  • Dure operators: sorts, hash builds, grote nested loops — deze tonen vaak waar het werk echt zit.

Als je maar één ding doet: identificeer de eerste operator waar het aantal rijen explodeert. Dat is meestal de kernoorzaak.

2) Veelvoorkomende antipatronen die optimizers verslaan

Deze zijn makkelijk te schrijven en verrassend kostbaar:

  • Functies op geïndexeerde kolommen: WHERE LOWER(email) = ... kan indexgebruik voorkomen (gebruik een genormaliseerde kolom of een functionele index als ondersteund).
  • Ontbrekende predicaten: vergeten van een datumbereik of tenant-filter verandert een gerichte query in een brede scan.
  • Per ongeluk cross joins: ontbrekende join-conditie kan rijen vermenigvuldigen en enorme tussenresultaten forceren.

3) Vorm een hypothese met algebraïsch denken

Relationele algebra moedigt twee praktische stappen aan:

  • Duw filters eerder: pas WHERE-condities toe vóór joins waar mogelijk om inputs te verkleinen.
  • Verminder kolommen vroeg: selecteer alleen benodigde kolommen (vooral vóór joins) om geheugen- en I/O-kosten te beperken.

Een goede hypothese klinkt als: “Deze join is duur omdat we te veel rijen joinen; als we orders eerst filteren op de laatste 30 dagen, daalt de join-input.”

4) Index, herschrijf of schemawijziging?

Gebruik een eenvoudige beslisregel:

  • Voeg een index toe wanneer de query correct, selectief en herhaaldelijk uitgevoerd wordt.
  • Herschrijf de query wanneer EXPLAIN vermijdbaar werk toont (onnodige joins, late filtering, non-sargable predicaten).
  • Wijzig het schema wanneer het workloadpatroon stabiel is en je steeds tegen dezelfde bottleneck vecht (bijv. voorgecomputeerde aggregaten, gedenormaliseerde lookup-velden of partitionering op tijd/tenant).

Het doel is geen “slimme SQL”, maar voorspelbaar kleinere tussenresultaten — precies de soort equivalence-preserving verbeteringen die Ullmans ideeën makkelijker maken om te herkennen.

Toepassen van deze ideeën bij productontwikkeling

Turn theory into a demo
Build a small Postgres app in chat and inspect the SQL your product will run.
Try Free

Deze concepten zijn niet alleen voor databasebeheerders. Als je een applicatie uitbrengt, maak je database- en query-planningsbeslissingen of je het weet: schema-vorm, sleutelkeuzes, query-patronen en de data-accesslaag beïnvloeden allemaal wat de optimizer kan doen.

Als je een vibe-coding workflow gebruikt (bijvoorbeeld het genereren van een React + Go + PostgreSQL-app vanuit een chatinterface in Koder.ai), zijn Ullman-achtige mentale modellen een praktisch vangnet: je kunt het gegenereerde schema controleren op schone sleutels en relaties, de queries bekijken waarop je app vertrouwt, en prestaties valideren met EXPLAIN voordat problemen in productie verschijnen. Hoe sneller je kunt itereren op “query-intentie → plan → fix”, hoe meer waarde je krijgt van versnelde ontwikkeling.

Waar je meer kunt leren en hoe je het op het werk toepast

Je hoeft theorie niet als een losse hobby te bestuderen. De snelste manier om te profiteren van Ullman-achtige fundamenten is genoeg leren om queryplannen vol vertrouwen te lezen — en daarna oefenen in je eigen database.

Beginnersvriendelijke bronnen om op te zoeken

Zoek naar deze boeken en lectuuronderwerpen (geen affiliatie — gewoon breed geciteerde startpunten):

  • “A First Course in Database Systems” (Ullman & Widom) — toegankelijke databasebasis met praktische kaders.
  • “Principles of Database and Knowledge-Base Systems” (Ullman) — diepere theorie als je meer rigour wilt.
  • “Compilers: Principles, Techniques, and Tools” (Aho, Lam, Sethi, Ullman) — voor de verbinding “waarom lijken optimizers op compilers?”.
  • Lecture/zoekonderwerpen: relationele algebra, query rewriting, join ordering, cost-based optimization, indexen en selectiviteit, parsing en querytalen.

Een lichte leerroute

Begin klein en koppel elke stap aan iets dat je kunt waarnemen:

  1. Relationele algebra: leer selectie, projectie, join en equivalentieregels.
  2. Plannen: leer plan-nodes lezen (scan-typen, filters, joins, sorts, aggregaten).
  3. Joins: begrijp nested loop vs hash join vs merge join en wanneer elk meestal wint.
  4. Kostmodellen: leer de paar inputs die beslissingen sturen (rijaantallen, selectiviteit, I/O vs CPU).

Kleine oefeningen die snel renderen

Kies 2–3 echte queries en iterereer:

  • Herschrijf: verander IN naar EXISTS, duw predicaten eerder, verwijder onnodige kolommen, vergelijk resultaten.
  • Vergelijk plannen: leg “voor/na” plannen vast en noteer wat er veranderde (join-volgorde, join-type, scan-type).
  • Varieer indexen: probeer één index tegelijk toe te voegen/verwijderen en bekijk geschatte vs werkelijke rijen.

Bevindingen communiceren met teamgenoten

Gebruik duidelijke, plan-gebaseerde taal:

  • “Het plan schakelde van een sequentiële scan naar een indexscan omdat de filter selectiever werd.”
  • “Rij-schattingen zaten er 100× naast, dus de optimizer koos de verkeerde join-volgorde.”
  • “Deze herschrijving is equivalent (zelfde resultaat), maar maakt predicate pushdown mogelijk en vermindert rijen in de join.”

Dat is de praktische winst van Ullmans fundamenten: je krijgt een gedeelde woordenschat om prestaties uit te leggen — zonder te gokken.

Veelgestelde vragen

Who is Jeffrey Ullman, and why does his work matter if I only write SQL?

Jeffrey Ullman heeft geholpen te formaliseren hoe databases de betekenis van queries beschrijven en hoe ze queries veilig kunnen transformeren naar sneller uitvoerbare vormen. Die fundering zie je elke keer dat een engine een query herschrijft, joins herordent of een andere uitvoering kiest terwijl het resultaat gelijk blijft.

What is relational algebra, and how is it connected to SQL?

Relationele algebra is een compacte set operatoren (selectie, projectie, join, unie, verschil) die precies beschrijven welke resultaten een query moet geven. Engines vertalen SQL vaak naar een algebra-achtige operatorboom zodat ze equivalentieregels (zoals filters naar voren schuiven) kunnen toepassen voordat ze een uitvoeringsstrategie kiezen.

Why do “meaning-preserving” query rewrites matter in practice?

Omdat optimalisatie afhankelijk is van het bewijzen dat een herschreven query hetzelfde resultaat oplevert. Equivalentieregels laten de optimizer dingen doen zoals:

  • WHERE-filters vóór een join plaatsen
  • ongebruikte kolommen vroeg wegnemen
  • joins herordenen wanneer dat logisch veilig is

Deze wijzigingen kunnen de hoeveelheid werk drastisch verminderen zonder de betekenis te veranderen.

What’s the difference between a logical query plan and a physical query plan?

Een logisch plan beschrijft wat er moet gebeuren (filter, join, aggregaat) onafhankelijk van opslagdetails. Een fysiek plan kiest hoe dat uitgevoerd wordt (indexscan vs volledige scan, hash join vs nested loop, parallelisme, sorteerstrategieën). De meeste prestatieverschillen ontstaan door fysieke keuzes die mogelijk worden gemaakt door logische herschrijvingen.

What is cost-based optimization in plain English?

Kosten-gebaseerde optimalisatie vergelijkt meerdere geldige plannen en kiest degene met de laagste geschatte kosten. Kosten worden meestal bepaald door praktische factoren zoals hoeveelheid verwerkte rijen, I/O, CPU en geheugen (bijvoorbeeld of een hash of sorteren naar schijf moet uitwijken).

What is cardinality estimation, and why does it cause unpredictable performance?

Cardinaliteitschattning is de inschatting van de optimizer: “hoeveel rijen komt dit stapje eruit?” Die schattingen bepalen joinvolgorde, jointype en of een indexscan de moeite waard is. Wanneer schattingen verkeerd zijn (vaak door verouderde of ontbrekende statistieken) kun je plotselinge vertragingen, grote spills of verrassende planwisselingen krijgen.

When should I expect nested loop, hash join, or merge join to be fastest?
  • Nested loop join: goed wanneer de linkerkant klein is en de rechterkant efficiënt kan worden doorzocht (vaak via een index).
  • Hash join: uitstekend voor grote gelijkheidsjoins op ongesorteerde data, maar heeft genoeg geheugen nodig om te voorkomen dat er naar schijf wordt uitgewisseld.
  • Merge join: sterk wanneer beide inputs al gesorteerd zijn (of goedkoop te sorteren), wat vaak wordt geholpen door indexen die rijen in join-key volgorde leveren.
How do I read an EXPLAIN plan without getting overwhelmed?

Richt je op een paar signalen met hoge informatiewaarde:

  • waar rijaantallen exploderen (de eerste grote toename is vaak de hoofdoorzaak)
  • geschatte versus werkelijke rijen (grote afwijkingen duiden op slechte statistieken/assumpties)
  • dure operators (grote sorts, hash builds, nested loops over grote inputs)
  • scankeuze (volledige scan wanneer je een indexlookup verwachtte)

Zie het plan als gecompileerde output: het laat zien wat de engine daadwerkelijk gekozen heeft.

How does normalization affect query performance, and when is denormalization acceptable?

Normalisatie vermindert gedupliceerde feiten en update-anomalieën, wat vaak resulteert in kleinere tabellen en indexen en betrouwbaardere joins. Denormalisatie kan toch de juiste keuze zijn voor analytics of herhaalde leesintensieve patronen, maar doe het doelbewust (met duidelijke verversregels en beheerde duplicatie) zodat correctheid niet wegzakt.

What techniques help queries stay fast as data scales without changing results?

Schaalbaarheid vereist vaak een andere fysieke strategie terwijl de querybetekenis identiek blijft. Veelgebruikte middelen zijn:

  • partitionering voor pruning en lokale verwerking
  • materialized views om equivalente subresultaten opnieuw te gebruiken
  • plannen aanpassen op basis van bijgewerkte statistieken na datagroei

Caching helpt herhaalde reads, maar lost geen probleem op wanneer een query te veel data moet aanraken of enorme tussenjoins produceert.

Inhoud
Waarom Ullman belangrijk is voor moderne dataDatabasefundamenten die Ullman heeft helpen verankerenRelationele algebra: de verborgen taal onder SQLVan SQL naar queryplannen: herschrijvingen die betekenis behoudenKosten-gebaseerde optimalisatie zonder jargonJoin-algoritmen en het hart van query-prestatiesCompilerideeën binnen database-enginesSchema-ontwerptheorie die echte prestaties raaktHoe theorie zichtbaar wordt als systemen schalenPraktische optimalisatiegewoonten geïnspireerd door UllmanToepassen van deze ideeën bij productontwikkelingWaar je meer kunt leren en hoe je het op het werk toepastVeelgestelde vragen
Delen
Koder.ai
Build your own app with Koder today!

The best way to understand the power of Koder is to see it for yourself.

Start FreeBook a Demo