Nieuws overzicht

August 21, 2025

7

min leestijd

Developer Steve deelt 4 tips voor een performantere database

Bij Teal Partners zijn we gespecialiseerd in complexe softwareprojecten. Databaseperformantie is daarbij cruciaal. Ontwikkelaar Steve Lievens legt uit waarom het zo belangrijk is — en hoe je er als ontwikkelaar vanaf dag één rekening mee houdt.

Bij veel softwareprojecten ligt de focus tijdens de ontwikkelfase op functionaliteit, design en gebruiksgemak. Als een feature werkt op de laptop van de ontwikkelaar en goed presteert in de testomgeving, dan is het klaar, toch? Niet helemaal.

Op de machine van de ontwikkelaar kan de applicatie vlot werken, omdat zijn lokale databank nauwelijks data bevat. In de testomgeving loopt het prima, omdat er maar weinig gebruikers tegelijk testen. Pas in productie blijkt of een app inefficiënt of traag is. Dan is het vaak lastig om het op te lossen.

Een goede index is goud waard.

Denk aan performantie vanaf dag één

Makkelijker is om vanaf het begin te ontwikkelen met performantie in gedachten. Bij projecten die snel opschalen of internationaal uitgerold worden, wordt dat nog belangrijker. Neem de Buddy-payrolloplossing voor SD Worx: die wordt uitgerold in verschillende Europese landen. Ondertussen zijn Duitsland, Luxemburg, België en Finland al actief. Het aantal gebruikers neemt voortdurend toe, aan een flink tempo. Performantie is een hoeksteen van dat succes.

Tijdens performance-audits komen we vaak dezelfde problemen tegen: overbodige joins, inefficiënte filters, query's die duizenden keren per minuut worden uitgevoerd, of systemen die vastlopen door deadlocks. Dat zijn problemen die je kunt vermijden.

Wees geen datablinde developer

In moderne applicaties wordt veel weggeabstraheerd. Developers werken met tools als NHibernate of Entity Framework, die automatisch objecten koppelen aan databanktabellen. Handig, maar daardoor verdwijnt het zicht op wat er écht gebeurt op de server.

Je schrijft code, krijgt een mooi persoon-object terug, en alles lijkt efficiënt. Maar zie je ook welke SQL-query’s worden uitgevoerd? Hoeveel data er opgehaald wordt? Gebeurt dat efficiënt? Vaak niet. En zo sluipen er onzichtbare problemen in: het klassieke N+1-queryprobleem bijvoorbeeld, waarbij voor elke gelinkte rij apart een query wordt uitgevoerd, terwijl één join-query veel efficiënter zou zijn.

Laat je tooling je niet blind maken. Als je software schrijft, moet je begrijpen wat er met je data gebeurt, op elk niveau.

TIP 1: Begrijp je indexen – clustered vs. non-clustered

Of data efficiënt wordt opgehaald, hangt sterk af van hoe je indexeert. Iedereen heeft al wel van indexen gehoord, maar weinig programmeurs kennen écht het verschil tussen clustered en non-clustered indexen. Zelfs tijdens technische interviews zien we dat keer op keer.

Stel je een databank voor als een boek:

  • Een clustered index is zoals de paginanummers van een boek. De data zelf staat fysiek op de schijf in die volgorde. Als je klant ‘420’ zoekt, blader je direct naar de juiste pagina. De sortering van de data is de index, dus je hebt maar één clustered index per tabel.
  • Een non-clustered index is zoals een alfabetische index achterin een boek. Je zoekt “Pieters”, leest dat die op pagina 248 staat, en bladert dan naar die pagina. Dat is handig, maar minder efficiënt, want er is een extra stap.
Een non-clustered index zit buiten de data. Vergelijk het met een inhoudstafel met een verwijzing van paginanummers.

Hoe groter je databank, hoe belangrijker dit verschil wordt. Slechte indexkeuzes leiden tot trage query's, onnodige table scans, en frustraties bij je gebruikers.

In veel databases wordt de clustered index standaard geplaatst op de primaire sleutel. Maar dat is niet verplicht, en soms is het zelfs een slecht idee. Bijvoorbeeld wanneer je GUIDs (UniqueIdentifiers) gebruikt als primary key. Die zijn handig: je kan ze in de applicatie genereren, joins zijn moeilijk verkeerd te leggen, en je kan nooit raden welk ID volgt of voorafging.

Maar er kleeft ook een nadeel aan: GUIDs zijn willekeurig opgebouwd. Ze zorgen ervoor dat nieuwe records niet netjes onderaan in je tabel komen, maar willekeurig tussen bestaande rijen worden ingevoegd. Gevolg: inserts worden trager, want bestaande rijen die alfabetisch ná de nieuwe rij komen, moeten worden opgeschoven om plaats te maken, en je performantie daalt.

Dat probleem kan je deels verhelpen door NEWSEQUENTIALID() te gebruiken. Die genereert opeenvolgende GUIDs in plaats van willekeurige.

Maar de meest robuuste aanpak? Maak je primary key non-clustered, en voeg een aparte identity-kolom toe waarop je de clustered index zet. Zo combineer je het beste van twee werelden: veilige ID’s én snelle toegang tot de data.

TIP 2: Begrijp je query plans

Je hebt een trage query, maar waar zit het probleem precies? Dat zie je in het query plan. Dit is de routekaart die de databank volgt om jouw query uit te voeren. Als je zo’n plan kunt lezen, zie je meteen waar het spaak loopt. Hoe je dat opent, hangt af van je database en tool.

Een query plan lees je van rechts naar links. De meest rechtse stappen tonen waar de data vandaan komt (bijvoorbeeld een tabel of index), en links zie je hoe de databank die data verwerkt.

Let ook op de pijlen tussen de stappen: hoe dikker de pijl, hoe meer rijen er door die stap gaan. Een dikke pijl op een onverwachte plek? Dat is vaak een waarschuwing dat er te veel data door de query stroomt. Dan kijk je best even of je joins of filters wel efficiënt zijn.

Drie soorten joins, drie strategieën

Als je query meerdere tabellen combineert, kiest de databank automatisch een join-algoritme.

  • Nested Loop Join: elke rij uit tabel A wordt gecombineerd met bijhorende rijen uit tabel B. Prima bij kleine datasets, maar traag bij grote.
  • Merge Join: efficiënt als beide tabellen al gesorteerd zijn. De databank stapt door beide tabellen tegelijk, wat snel werkt voor gestructureerde data.
  • Hash Match: gebruikt een ‘hashtabel’ om matches te vinden. Goed bij ongelijk verdeelde data, maar kan veel geheugen vragen.

Als je een join ziet die niet logisch is voor je datavolume of filters, weet je: hier kunnen we misschien optimaliseren.

Index Seek, Index Scan, Table Scan

Dit is het meest directe signaal in een query plan over hoe efficiënt je query is. Vuistregel? Seek is goed, scan is oké, maar table scan wil je vermijden.

  • Index Seek: de databank weet exact wat ze zoekt en springt direct naar de juiste plaats in de index. Supersnel.
  • Index Scan: de databank leest de hele index, ook al is maar een deel van de data nodig. Minder efficiënt.
  • Table Scan: de databank leest élke rij in de tabel. Een duidelijke rode vlag dat er geen goede index bestaat of dat de query herschreven moet worden.

TIP 3: Profiling is gratis (en goud waard)

Veel developers beseffen het niet, maar elke moderne databank biedt tools aan om je query's te analyseren. Die zijn perfect om regressies of onverwachte performantieproblemen op te sporen.

Query Plan analyseert hoe de database-engine een query uitvoert. Je ziet precies welke query's traag zijn, welke veel resources opsouperen of welke indexes overbodig zijn.

Query Store bewaart een historiek van je query's, zodat je kunt zien (1) welke query's het meest resources verbruiken, (2) welke trager zijn geworden sinds een wijziging, en (3) wanneer een query ineens een ander query plan gebruikt.

Wil je nog meer? De First Responder Kit van Brent Ozar is een gratis toolkit met scripts. Die geven je in één oogopslag inzicht in trage query's, overbodige indexen, verkeerd geheugengebruik en meer.

TIP 4: Begrijp de problemen

Soms merk je niet alleen vertraging, maar blokkeert het hele systeem. Gebruikers kunnen geen gegevens meer bewaren, transacties blijven hangen. Dan is de kans groot dat er een lockingprobleem speelt.

Wanneer je een rij in een databank aanpast, wordt die tijdelijk vergrendeld om gelijktijdige wijzigingen te voorkomen. Dat is normaal gedrag. Maar als je duizenden rijen tegelijk probeert te wijzigen, kan de databank besluiten om al die individuele locks te vervangen door een groter geheel. Dat proces heet lock escalation.

Lock escalation verloopt in stappen: eerst wordt een individuele rij vergrendeld, een row lock. Als dat er te veel worden, schakelt de databank over naar een page lock: een volledige blok van rijen. In het uiterste geval volgt een table lock, waarbij de hele tabel wordt vergrendeld.

Bij een table lock kunnen ook andere gebruikers of processen geen bewerkingen meer uitvoeren op die tabel. Dat kan de hele applicatie vertragen of blokkeren.

Hoe locking werkt (en waarom het soms spaak loopt)

Bij elke wijziging in de databank zet de engine een slot op de betrokken data, zodat er geen tegenstrijdige bewerkingen tegelijk plaatsvinden. Meestal werkt dat prima. Maar soms blokkeren processen elkaar.

Een mutual exclusive lock (kortweg: mutex) betekent dat slechts één proces tegelijk toegang krijgt tot een bepaald stukje data. Iedereen die tegelijk iets wil doen met die data, moet wachten. Zo voorkom je dat gegevens overschreven of corrupt raken.

Maar mutex-locks kunnen ook vastlopen. Dat gebeurt bij een deadlock: twee processen houden elkaar in een houdgreep omdat elk wacht op een lock die de ander vasthoudt. De databank lost dat zelf op door één van de twee transacties af te breken. Die wordt de deadlock victim genoemd.

Standaard kiest de engine degene waarvoor het het minst zwaar is om terug te draaien — bijvoorbeeld omdat die transactie nog maar net gestart was of weinig data had vergrendeld.

Wie regelmatig met deadlocks te maken krijgt, bekijkt best zijn transactiestructuur of isolatieniveaus. Vaak helpt het al om altijd in dezelfde volgorde te locken, of om minder lang locks vast te houden.

Aan het einde van elke maand wisselen onze developers kennis en knowhow uit tijdens de Teal Fridays, bijvoorbeeld over databaseperformantie of andere IT-topics.

Tot slot: maak van performantie een teameffort

Wat wij aanraden: laat wekelijks iemand uit je team de top 10 slechtst presterende query's bekijken. Gebruik bijvoorbeeld de Query Store of sp_BlitzCache om automatisch de zwaarste query's van de voorbije week te detecteren. Zijn er te veel indexen? Of net te weinig? Zijn er regressies in de performance? Waar kunnen we verbeteren?

Het kost je maar een uurtje tijd, maar levert vaak enorme winst op.

Performantie is geen randvoorwaarde; het is een kernonderdeel van goede software. Als je toekomstbestendige software wil schrijven, dan is performantie de verantwoordelijkheid van elk teamlid — niet alleen van de DBA.

In deel 2 gaat Steve weldra technisch dieper in op de kwestie. Stay tuned. Wil je ondertussen al eens van gedachten wisselen met Steve? Stuur hem een bericht via steve@tealpartners.com.