{"id":20930,"date":"2026-08-23T15:05:18","date_gmt":"2026-08-23T13:05:18","guid":{"rendered":"https:\/\/webhosting.de\/mariadb-query-optimizer-intern-erklaert-sql-tuning-insight\/"},"modified":"2026-08-23T15:05:18","modified_gmt":"2026-08-23T13:05:18","slug":"een-interne-uitleg-van-de-mariadb-query-optimizer-inzichten-in-sql-tuning","status":"publish","type":"post","link":"https:\/\/webhosting.de\/nl\/mariadb-query-optimizer-intern-erklaert-sql-tuning-insight\/","title":{"rendered":"Een interne uitleg over de MariaDB Query Optimizer: basisprincipes, plannen en praktijk"},"content":{"rendered":"<p>Ik leg het uit <strong>MariaDB-optimalisator<\/strong> uit de praktijk: hoe hij plannen opstelt, kosten inschat en waarom hij er soms naast zit. Zo lees je het SQL-uitvoeringsplan doelgericht, zet je indexen op een zinvolle manier in en stuur je de optimizer aan met feiten in plaats van op gevoel.<\/p>\n\n<h2>Centrale punten<\/h2>\n\n<p>Om te beginnen zal ik de belangrijkste onderdelen kort samenvatten, zodat je de volgende paragrafen beter kunt plaatsen en de <strong>Overzicht<\/strong> behoudt.<\/p>\n<ul>\n  <li><strong>Fasen<\/strong>: Parsen, voorbereiden, optimaliseren en uitvoeren vormen de levenscyclus van elke query.<\/li>\n  <li><strong>Kostenmodel<\/strong>: Op tijd gebaseerde waarden in microseconden bepalen de indexkeuze, scans en de volgorde van joins.<\/li>\n  <li><strong>Statistieken<\/strong>: De cardinaliteit en het histogram zijn bepalend voor de schatting van de selectiviteit.<\/li>\n  <li><strong>Transparantie<\/strong>: EXPLAIN, EXPLAIN ANALYZE en Optimizer Trace openen de blackbox.<\/li>\n  <li><strong>Afstemmen<\/strong>: Indexen, query-rewrites, ANALYZE TABLE en kostenparameters zorgen voor snelheid.<\/li>\n<\/ul>\n\n\n<figure class=\"wp-block-image size-full is-resized\">\n  <img fetchpriority=\"high\" decoding=\"async\" src=\"https:\/\/webhosting.de\/wp-content\/uploads\/2026\/08\/mariaDB-query-plans-9842.png\" alt=\"\" width=\"1536\" height=\"1024\"\/>\n<\/figure>\n\n\n<h2>Levenscyclus van een query in MariaDB<\/h2>\n\n<p>Voordat er een plan ontstaat, doorloopt een query vier fasen, die ik in de dagelijkse praktijk doelgericht controleer om <strong>Oorzaken<\/strong> om traagheid op te sporen. Tijdens het parseren zet MariaDB SQL om in een interne structuur; syntaxisfouten vallen hier op. In de voorbereidingsfase controleert de engine tabellen, kolommen en mogelijke indexen en voert eenvoudige transformaties uit. Vervolgens vindt de optimalisatie plaats, waarbij kandidaat-plannen worden berekend en aan de hand van een kostenmodel worden beoordeeld. Tijdens de uitvoering voert de server het gekozen plan stap voor stap uit: lezen, samenvoegen, filteren, teruggeven.<\/p>\n\n<p>Ik maak een duidelijk onderscheid tussen analysefouten per fase, omdat diagnoses op die manier sneller effect sorteren en <strong>Maatregelen<\/strong> doelgericht te werk gaan. Meestal liggen prestatieproblemen in de optimalisatie: verkeerde schattingen, ontbrekende indexen of ongunstige volgordes van joins. Parsingfouten zijn triviaal, maar de voorbereidingsfase kan al finesses bevatten zoals het oplossen van views of het herschikken van subquery\u2019s. Tijdens de uitvoering vallen ineffici\u00ebnties genadeloos op als er eerder voor een volledige scan is gekozen. Daarom begin ik elk onderzoek met een gestructureerd overzicht van alle vier de fasen.<\/p>\n\n<h2>Hoe de Optimizer intern beslissingen neemt<\/h2>\n\n<p>MariaDB hanteert een op kosten gebaseerde aanpak en beoordeelt alternatieve uitvoeringen aan de hand van een <strong>Kostenfunctie<\/strong>. Voor elke variant maakt de server een schatting van het aantal gelezen rijen, de selectiviteit van WHERE\/ON, toegangsmethoden zoals table scan, index scan en range scan, en de tijd die afzonderlijke bewerkingen in beslag nemen. Intern maakt de server onderscheid tussen join_preparation en join_optimization. In join_preparation vinden query-herschrijvingen, vereenvoudigingen van voorwaarden, herformuleringen van subquery's en het oplossen van views plaats. join_optimization berekent join-volgordes, controleert indexkandidaten via ref_optimizer_key_uses, schat het aantal rijen via range-scans en wijst voorwaarden zo vroeg mogelijk toe aan specifieke tabellen.<\/p>\n\n<p>Dit mechanisme verklaart waarom een klein filter op de verkeerde plek dure <strong>Gevolgen<\/strong> heeft. Als `attaching_conditions_to_tables` laat plaatsvindt, sleept het plan onnodig veel rijen door joins. Als statistieken verouderd zijn, komen `rows_estimation` en `Selectivity` verkeerd uit; de optimizer kiest dan voor gunstige, maar in werkelijkheid trage toegangspaden. Ik pak precies deze knoppen aan: betere statistieken, duidelijkere predicaten, netjes gesorteerde samengestelde indexen. Daarna verandert de keuze van het plan vaak merkbaar.<\/p>\n\n\n<figure class=\"wp-block-image size-full is-resized\">\n  <img decoding=\"async\" src=\"https:\/\/webhosting.de\/wp-content\/uploads\/2026\/08\/MariaDBQueryOptKonferenz1234.png\" alt=\"\" width=\"1536\" height=\"1024\"\/>\n<\/figure>\n\n\n<h2>Kostenmodel vanaf MariaDB 11.0<\/h2>\n\n<p>In de nieuwste releases wordt het werk niet meer grofweg op basis van gewichten beoordeeld, maar met <strong>microseconden<\/strong> voor specifieke opslagbewerkingen. Parameters zoals optimizer_disk_read_cost, optimizer_disk_read_ratio en optimizer_where_cost brengen het model dichter bij de werkelijke uitvoeringstijden. Zo vergelijkt de optimizer een index-range-scan met een volledige scan op basis van re\u00eble tijdsveronderstellingen. LAST_QUERY_COST geeft de geschatte totale kosten weer en komt vaak aanzienlijk beter overeen met de werkelijkheid dan voorheen. Voor gegevensintensieve systemen loont dit fijnmazigere raster zich onmiddellijk.<\/p>\n\n<p>Ik kalibreer het model zorgvuldig wanneer hardware-eigenschappen de standaardaannames tegenspreken en daarmee de <strong>Plankeuze<\/strong> vervormen. NVMe-SSD\u2019s, gedistribueerde opslag of speciale caches kunnen de schijverhouding en leestijden merkbaar be\u00efnvloeden. Kleine aanpassingen aan `optimizer_costs` zorgen ervoor dat MariaDB de voorkeur geeft aan zinvolle paden. Ik documenteer elke wijziging en controleer vervolgens EXPLAIN ANALYZE om het effect te meten. Zonder metingen blijft tuning een gok.<\/p>\n\n<h2>Selectiviteit, statistieken en histogrammen<\/h2>\n\n<p>Goede schattingen beginnen met een goede <strong>cardinaliteit<\/strong> en betrouwbare selectiviteit. MariaDB houdt statistieken bij van verschillende waarden per kolom en kan optioneel gebruikmaken van histogrammen voor verdelingen. Juist ongelijkmatige gegevens \u2013 hotspots, Zipf-verdelingen, seizoenspatronen \u2013 profiteren van histogrammen. Na grote gegevenswijzigingen voer ik ANALYZE TABLE uit, zodat de optimalisatie weer op basis van feiten werkt. Wie dit vergeet, riskeert volledige scans die objectief gezien onjuist zijn.<\/p>\n\n<p>Ik plan ANALYZE in als een regelmatige taak, afgestemd op <strong>Veranderingen<\/strong> in het gegevensvolume en op kritieke tabellen. Bij sterk scheve kolomverdelingen helpen histogrammen om de selectiviteit van afwijkende waarden realistisch in kaart te brengen. Dit vermindert verkeerde inschattingen bij range-scans en merge-strategie\u00ebn. In combinatie met geschikte samengestelde indexen neemt de trefnauwkeurigheid drastisch toe. Resultaat: kortere looptijden en minder I\/O.<\/p>\n\n\n<figure class=\"wp-block-image size-full is-resized\">\n  <img decoding=\"async\" src=\"https:\/\/webhosting.de\/wp-content\/uploads\/2026\/08\/mariadb-query-optimizer-guide-4729.png\" alt=\"\" width=\"1536\" height=\"1024\"\/>\n<\/figure>\n\n\n<h2>EXPLAIN en uitvoeringsplannen lezen<\/h2>\n\n<p>Om beslissingen zichtbaar te maken, gebruik ik EXPLAIN, EXPLAIN EXTENDED en <strong>FORMAT=JSON<\/strong>. De klassieke kolommen geven een snel overzicht: id, select_type, table, type, possible_keys, key, key_len, ref, rows en eventueel filtered. Een type=ALL duidt op een volledige scan, wat zelden gewenst is. FORMAT=JSON toont gedetailleerd hoe voorwaarden zijn verplaatst en welke paden de optimizer heeft ge\u00ebvalueerd. In de context van hosting raad ik de handleiding aan over <a href=\"https:\/\/webhosting.de\/nl\/uitvoeringsplannen-van-databasequerys-hosting-inzichten-in-optimalisatieprestaties\/\">Uitvoeringsplannen bij hosting<\/a>, om informatie over plannen te koppelen aan infrastructuureffecten.<\/p>\n\n<p>Om snel een beeld te krijgen, gebruik ik een kleine tabel waarin de typische waarden kort worden weergegeven, en daarmee <strong>Misinterpretaties<\/strong> voorkomen.<\/p>\n\n<table>\n  <thead>\n    <tr>\n      <th>EXPLAIN-veld<\/th>\n      <th>Typische waarde<\/th>\n      <th>Betekenis in de praktijk<\/th>\n    <\/tr>\n  <\/thead>\n  <tbody>\n    <tr>\n      <td>type<\/td>\n      <td>ALL, range, ref, eq_ref, const<\/td>\n      <td>Hoe verder naar rechts, hoe selectiever; ALL geeft aan dat er een volledige scan wordt uitgevoerd.<\/td>\n    <\/tr>\n    <tr>\n      <td>possible_keys<\/td>\n      <td>Indexlijst<\/td>\n      <td>Indexen die in theorie kloppen; als er hier kandidaten ontbreken, ontbreekt er structuur.<\/td>\n    <\/tr>\n    <tr>\n      <td>sleutel<\/td>\n      <td>Indexnaam<\/td>\n      <td>Daadwerkelijk gebruikte index; leeg betekent dat er geen index wordt gebruikt.<\/td>\n    <\/tr>\n    <tr>\n      <td>rijen<\/td>\n      <td>Aantal<\/td>\n      <td>Geschat aantal gelezen regels; wijkt sterk af van de werkelijkheid = slechte statistiek.<\/td>\n    <\/tr>\n    <tr>\n      <td>gefilterd<\/td>\n      <td>Procent<\/td>\n      <td>Hoeveel er na het filter wordt doorgelaten; weinig is vaak goed.<\/td>\n    <\/tr>\n  <\/tbody>\n<\/table>\n\n<h2>Waarom de Optimizer er soms naast zit<\/h2>\n\n<p>Geen enkel kostenmodel is geschikt voor elke situatie, daarom pas ik het aan <strong>Fouten<\/strong> gericht. Verouderde statistieken leiden tot onjuiste schattingen van het aantal rijen en ongunstige volgordes van joins. Verkeerd opgebouwde samengestelde indexen verhinderen het gebruik van indexen bij filters met meerdere kolommen. Zeer geneste subquery's bemoeilijken effectieve herschrijvingen en blokkeren materialisatie. Ontbrekende of misleidende filters dwingen de engine om veel rijen te verplaatsen voordat bruikbare predicaten van kracht worden.<\/p>\n\n<p>Ik controleer eerst of de formulering van de zoekopdracht voldoet aan de <strong>Index<\/strong> Wat echt helpt: de regel voor linkse prefixen, de juiste sorteervolgorde, het vermijden van functies op kolommen in de WHERE-clausule. Daarna kijk ik in EXPLAIN ANALYZE of de werkelijkheid de schatting bevestigt. Zo niet, dan volg ik met ANALYZE TABLE en, indien nodig, een herschrijving. Pas als laatste gebruik ik FORCE INDEX of hinting, omdat dit toekomstige optimalisaties kan beperken.<\/p>\n\n\n<figure class=\"wp-block-image size-full is-resized\">\n  <img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/webhosting.de\/wp-content\/uploads\/2026\/08\/mariadboptimizer_2219.png\" alt=\"\" width=\"1536\" height=\"1024\"\/>\n<\/figure>\n\n\n<h2>Optimizer Trace doelgericht gebruiken<\/h2>\n\n<p>Als EXPLAIN niet volstaat, schakel ik de Optimizer Trace in en volg ik <strong>Beslissingen<\/strong> in het JSON-logboek. Daarin zie ik welke plannen zijn overwogen, afgewezen of geaccepteerd. Ik begrijp waarom een voorwaarde pas laat van kracht wordt of waarom een index niet in aanmerking is gekomen. Het logboek laat ook zien hoe voorwaarden zijn herschikt. Dit inzicht verscherpt het begrip en biedt concrete aanknopingspunten voor de volgende optimalisatie.<\/p>\n\n<p>Ik sla relevante delen van de trace op, samen met de query-hash en <strong>Parameters<\/strong>waarderen. Zo kan ik later vergelijken welke wijziging welk effect heeft gehad. De documentatie van de MariaDB-server en diverse presentaties binnen het ecosysteem beschrijven de velden uitvoerig (bron: MariaDB-serverdocumentatie over de Query Optimizer en Optimizer Trace). Met deze tool vind ik onjuiste aannames sneller dan met trial-and-error. Ik bespaar vooral tijd bij complexe joins.<\/p>\n\n<h2>Praktijk: Database-optimalisatie stap voor stap<\/h2>\n\n<p>Ik begin elke optimalisatie met een duidelijke <strong>Meting<\/strong>. Ik identificeer probleemmeldingen via monitoring en het <a href=\"https:\/\/webhosting.de\/nl\/mysql-trage-query-log-hosting-analyseer-queryperf\/\">Logboek langzame zoekopdrachten<\/a>. Vervolgens vergelijk ik EXPLAIN met EXPLAIN ANALYZE om het plan en de werkelijkheid naast elkaar te leggen. Ik pas de indexstrategie aan op WHERE, JOIN en ORDER BY; samengestelde indexen richt ik af op de meest voorkomende toegangspunten. FORCE INDEX gebruik ik alleen als de optimizer ondanks correcte statistieken de verkeerde kandidaat kiest.<\/p>\n\n<p>Bij elke stap hoort het onderhoud van de <strong>Statistieken<\/strong>: ANALYZE TABLE op tabellen met veel activiteit, histogrammen voor scheve verdelingen. Ik vereenvoudig overbodige subquery\u2019s, materialiseer tussentijdse resultaten indien nodig en ruim oude workarounds op. Bij speciale hardware controleer ik de optimizer_costs, zodat het microsecondenmodel klopt. Elke wijziging documenteer ik met waarden van voor en na, zodat het effect blijvend traceerbaar blijft.<\/p>\n\n\n<figure class=\"wp-block-image size-full is-resized\">\n  <img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/webhosting.de\/wp-content\/uploads\/2026\/08\/mariadb_query_optimizer_8390.png\" alt=\"\" width=\"1536\" height=\"1024\"\/>\n<\/figure>\n\n\n<h2>Typische problemen met optimalisatieprogramma's en oplossingen<\/h2>\n\n<p>Als EXPLAIN type=ALL aangeeft dat possible_keys gevuld is, kijk ik eerst naar <strong>Selectiviteit<\/strong>. Vaak klopt de kolomvolgorde in de samengestelde index niet, of verhindert een functie het gebruik van de index. Dan draai ik de volgorde om, verwijder ik storende functies of splits ik predicaten. Bij een verkeerde volgorde van joins controleer ik of vroegtijdig filteren mogelijk is, bijvoorbeeld door de selectievere tabel eerder te plaatsen. Subquery\u2019s zet ik, waar zinvol, om in joins of TEMPORARY-tabellen.<\/p>\n\n<p>Ik herken verkeerde beslissingen ook aan sterk afwijkende <strong>rijen<\/strong> tussen plan en werkelijkheid. Dan helpt `ANALYZE TABLE` of een histogram voor de betreffende kolom. Als zelfs correcte statistieken niet tot het gewenste resultaat leiden, overweeg ik expliciete hints. Vooraf zorg ik voor een controlemeting en meetwaarden, zodat latere versies van de optimizer niet onnodig worden afgeremd. Discipline bij het documenteren loont hier de moeite.<\/p>\n\n<h2>Hostingcontext en operationele aspecten<\/h2>\n\n<p>De kwaliteit van de zoekopdrachten en de infrastructuur moeten op elkaar zijn afgestemd, anders gaat de toepassing zonde verloren <strong>Potentieel<\/strong>. Snelle SSD\u2019s, consistente caches en een nette configuratie vormen de basis waarop de Optimizer goede beslissingen neemt. Bij veel verkeer is er geen ruimte voor volledige scans; een paar slechte query\u2019s kunnen hele systemen vertragen. Voor MySQL\/MariaDB-omgevingen in productie bieden we praktische tips zoals <a href=\"https:\/\/webhosting.de\/nl\/mysql-optimizer-query-hosting-optimalisatie-serverboost\/\">MySQL-optimalisator<\/a> nuttige aandachtspunten over de combinatie van plan en platform. Wie op dit niveau meedenkt, voorkomt knelpunten voordat ze uit de hand lopen.<\/p>\n\n<p>Ik koppel plananalyse altijd aan statistieken over <strong>I\/O<\/strong>, latentie en gelijktijdigheid. Als de waarden niet overeenkomen met het aangenomen kostenmodel, controleer ik de parameters. Daarna kijk ik naar de grootte van de buffers, parallelle workloads en de verdeling van de hotsets. Op deze manier lukt het om query's en resources op een evenwichtige manier te laten draaien en pieken onder controle te houden.<\/p>\n\n<h2>Join- en toegangspaden in de praktijk<\/h2>\n\n<p>Veel misverstanden neem ik weg door de <strong>Toegangstypen<\/strong> doelgericht tegen elkaar afwegen. Een <em>bereik<\/em>- of <em>ref<\/em>-Toegang lukt bijna altijd <em>ALLES<\/em>. Bij logische koppelingen op unieke sleutels (<em>eq_ref<\/em>) zijn de plannen bijzonder stabiel. Ik controleer bovendien of een <strong>Dekkingsindex<\/strong> de query volledig afhandelt: als alle benodigde kolommen in de index staan, bespaart MariaDB dure tabeltoegangen. <strong>Indexvoorwaarde Pushdown (ICP)<\/strong> helpt om extra WHERE-voorwaarden al in de index te controleren \u2013 dit vermindert het aantal geretourneerde rijen en de I\/O.<\/p>\n\n<p>Over <strong>Index samenvoegen<\/strong> kan MariaDB meerdere indexen combineren (intersectie\/unie). Dat is handig bij OR-predicaten of meerdere selectievoorwaarden, maar vaak langzamer dan een goed gekozen samengestelde index. Ik evalueer bovendien <strong>MRR<\/strong> (Multi-Range Read) en <strong>BKA<\/strong> (Batched Key Access). MRR sorteert de te lezen primaire sleutels om willekeurige I\/O te egaliseren; BKA bundelt join-lookups en levert vooral voordelen op bij niet-overlappende joins. In de praktijk test ik BKA\/MRR via optimizer_switch en controleer ik met EXPLAIN ANALYZE of de I\/O-patronen afnemen. Als MariaDB daarentegen kiest voor de <strong>Blok met geneste lussen<\/strong> (BNL), is het meestal de moeite waard om de join-buffer (join_buffer_size) te vergroten \u2013 of een rewrite te gebruiken die echte index-joins mogelijk maakt.<\/p>\n\n<pre><code>-- Voorbeeld: samengestelde index voor join + filter + sortering\nCREATE INDEX ix_orders_cust_status_created\n  ON orders (customer_id, status, created_at);\n\n-- Typische opvraging\nSELECT *\nFROM orders o\nJOIN customers c ON c.id = o.customer_id\nWHERE o.status = 'open' AND o.created_at &gt;= '2026-01-01'\nORDER BY o.created_at DESC\nLIMIT 50;\n<\/code><\/pre>\n\n<p>Met bovenstaande index kan de optimizer de meest selectieve volgorde kiezen, filters vroegtijdig evalueren en de sortering vaak uitvoeren zonder extra bestandssortering.<\/p>\n\n<h2>ORDER BY, GROUP BY, Filesort en tijdelijke tabellen<\/h2>\n\n<p>Sorteren en samenvoegen kost tijd. Ik zorg ervoor dat <strong>ORDER BY<\/strong> en <strong>GROEP DOOR<\/strong> op de indexvolgorde kunnen lopen. Dit lukt als het voorvoegsel en de richting exact overeenkomen. Anders treedt een <strong>Bestands sorteren<\/strong> met sorteerbuffer (sort_buffer_size) en eventueel een tijdelijke tabel. Als de resultaten set brede TEXT\/BLOB-kolommen bevat, valt MariaDB sneller op <em>op schijf<\/em> TEMP-tabellen (Aria). Ik neem dit voor door alleen de benodigde kolommen te selecteren, grote velden pas aan het einde te laden of met prefixen van beperkte lengte te werken.<\/p>\n\n<p>Bij aggregaties gebruik ik, waar mogelijk, <strong>Losse indexscan<\/strong> (bijv. GROUP BY op het leidende indexgedeelte) en kies samengestelde indexen langs de groepering. Wanneer tussentijdse resultaten groot worden, schaalt een materialisatie met zinvolle sleutels beter dan \u00e9\u00e9n enkele mega-join. Ik meet regelmatig handler-statistieken en Created_tmp_*-tellers om hotspots bij het sorteren en in tijdelijke tabellen op te sporen.<\/p>\n\n<h2>Subquery's, semi-join en materialisatie<\/h2>\n\n<p>Veel subquery\u2019s kunnen tijdens de voorbereiding effici\u00ebnt worden herschreven. IN\/EXISTS-constructies kunnen worden omgezet in <strong>Semi-join<\/strong> werken, met strategie\u00ebn zoals materialisatie of LooseScan. Ik controleer of de optimizer een <strong>afgeleide samenvoeging<\/strong> kon uitvoeren: als een afgeleide tabel (of een WITH-CTE) in het buitenste plan wordt opgenomen, zijn de bijbehorende indexen direct beschikbaar. Als dat niet lukt, komt de subquery in een tijdelijke tabel terecht \u2013 ik geef deze dan, indien mogelijk, een sleutel (bijvoorbeeld door SELECT DISTINCT\/ORDER BY op sleutelkolommen), zodat joins daarop niet in het niets verdwijnen.<\/p>\n\n<pre><code>-- Voorbeeld: EXISTS in plaats van IN en een afgeleide tabel die geschikt is voor een merge\nSELECT o.id\nFROM orders o\nWHERE EXISTS (\n  SELECT 1 FROM payments p\n  WHERE p.order_id = o.id AND p.state = 'captured'\n);\n\n-- Afgeleide tabel met unieke sleutels\nWITH paid_orders AS (\n  SELECT DISTINCT order_id\n  FROM payments\n  WHERE state = 'captured'\n)\nSELECT o.*\nFROM orders o\nJOIN paid_orders po ON po.order_id = o.id;\n<\/code><\/pre>\n\n<p>Ik controleer met EXPLAIN FORMAT=JSON of <strong>gematerialiseerd<\/strong> of <strong>afhankelijke subquery<\/strong> werd gekozen en of er voorwaarden (<strong>condition pushdown<\/strong>) er vroeg genoeg bij zijn.<\/p>\n\n<h2>Partitionering en snoeien<\/h2>\n\n<p>Partitionering is geen vervanging voor indexen, maar kan de <strong>Hoeveelheid gegevens per toegang<\/strong> drastisch verminderen. De Optimizer voert alleen een zuivere pruning uit als het predikaat de <strong>Partitiesleutel<\/strong> duidelijk overeenkomt en niet door functies onherkenbaar wordt gemaakt. Ik vermijd daarom uitdrukkingen als DATE(created_at) in de WHERE-clausule bij gepartitioneerde tabellen en werk in plaats daarvan met bereikgrenzen. EXPLAIN laat zien welke partities worden gelezen; brede bereiken duiden op slechte pruning.<\/p>\n\n<p>Te veel kleine partities verhogen de planningsoverhead. Ik kies daarom voor een zinvolle granulariteit (bijvoorbeeld maandelijks in plaats van dagelijks), houd de statistieken per partitie up-to-date (ANALYZE PARTITION) en controleer of belangrijke indexen lokaal in de partities aanwezig zijn. Bij migratieprojecten houd ik rekening met de invloed op replicatie en back-up \u2013 beide factoren bepalen hoe agressief ik partitioneer.<\/p>\n\n<h2>Sargability en rewrite-patronen<\/h2>\n\n<p>De eenvoudigste hefboom blijft <strong>Sargability<\/strong> \u2013 Voorwaarden die indexen bruikbaar maken. Ik vermijd functies op kolommen in de WHERE-clausule, breng constanten terug naar de kolomzijde en splits OR-voorwaarden indien nodig op in <strong>UNIE ALLE<\/strong>. Voor LIKE-zoekopdrachten zonder voorvoegsel (\"%foo\") is een BTREE-index niet geschikt; hiervoor ben ik van plan om full-text of een geschikte zoekdienst te gebruiken. Voor berekeningen maak ik gebruik van <strong>ge\u00efndexeerde gegenereerde kolommen<\/strong>, zodat de optimizer de logica in de index kan terugvinden.<\/p>\n\n<pre><code>-- Anti-patroon: functie op kolom\nWHERE DATE(created_at) = '2026-08-01'\n-- Beter: bereik op ruwe waarde\nWHERE created_at &gt;= '2026-08-01' AND created_at &lt; &#039;2026-08-02&#039;\n\n-- Anti-patroon: OR verhindert index\nWHERE status = &#039;open&#039; OR customer_id = 42\n-- Beter: twee zoekopdrachten met UNION ALL en elk een eigen index\n(SELECT ... WHERE status = &#039;open&#039;)\nUNION ALL\n(SELECT ... WHERE customer_id = 42&#039;);\n<\/code><\/pre>\n\n<p>Wat samengestelde indexen betreft, ben ik van mening dat de <strong>regel voor het linkervoorvoegsel<\/strong> Houd je strikt aan de regels: rangschik kolommen op selectiviteit en op de sortering die later nodig is. Als ik een aflopende ORDER BY nodig heb, houd ik daar rekening mee in de indexindeling \u2013 zo bespaar ik mezelf de bestandssortering.<\/p>\n\n<h2>Optimizer-schakelaar en fijnafstemming van de kosten<\/h2>\n\n<p>Voordat ik aan query's ga werken, controleer ik <strong>optimizer_switch<\/strong> en opslagbuffers. Functies zoals <em>mrr<\/em>, <em>batched_key_access<\/em>, <em>index_merge<\/em>, <em>semijoin<\/em>, <em>afgeleide samenvoeging<\/em> of <em>condition_pushdown_for_afgeleide<\/em> kunnen per sessie worden aangepast. Ik activeer kandidaten gericht voor een testsessie, meet met EXPLAIN ANALYZE en draai de wijzigingen terug als het gewenste effect uitblijft. Het join-pad profiteert van voldoende <strong>samenvoegen_buffer_grootte<\/strong>; grote soorten van <strong>sorteer_buffer_grootte<\/strong>. Tegelijkertijd houd ik de buffers in de gaten in verhouding tot de gelijktijdigheid, zodat de server bij parallelle belasting niet gaat swappen.<\/p>\n\n<p>Op kostenvlak pas ik, indien nodig, de eerder genoemde <strong>optimalisatiekosten<\/strong> in microseconden. Mijn aanpak: kleine, omkeerbare stappen met gedocumenteerde meetpunten. Ik maak gebruik van <strong>LAST_QUERY_COST<\/strong> om de plausibiliteit te controleren en herhaal metingen met realistische parameterwaarden, omdat plannen sterk afhankelijk kunnen zijn van concrete letterlijke waarden.<\/p>\n\n<h2>Planstabiliteit, regressies en teamworkflow<\/h2>\n\n<p>Zelfs een goed plan kan door de toename van gegevens of een versiewisseling <strong>omvallen<\/strong>. Daarom verzamel ik informatie over de uitvoeringsplannen: query-hashes, EXPLAIN-JSON, fragmenten uit de optimizer-trace en de uitvoeringstijden van EXPLAIN ANALYZE. Wijzigingen aan indexen en herschrijvingen voer ik uit via een pull-request, met voor-en-na-bewijsmateriaal. In CI\/CD-omgevingen toets ik kritieke query's automatisch aan de hand van representatieve gegevenssets. Zo pak ik <strong>Regressieplannen<\/strong> vroeg.<\/p>\n\n<p>Voor lastige gevallen houd ik <strong>Tips<\/strong> (FORCE INDEX, STRAIGHT_JOIN, optimizer_switch per query) zijn beschikbaar als laatste optie, maar gebruik ze spaarzaam en met een vervaldatum. Het is beter om de oorzaken \u2013 statistieken, indexen, formulering \u2013 aan te pakken. In teams zorgt een beknopte richtlijn voor schaalbaarheid, indexontwerp en meetdiscipline ervoor dat nieuwe functies niet onopgemerkt prestatieproblemen veroorzaken.<\/p>\n\n\n<figure class=\"wp-block-image size-full is-resized\">\n  <img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/webhosting.de\/wp-content\/uploads\/2026\/08\/mariadb-query-optimizer-7832.png\" alt=\"\" width=\"1536\" height=\"1024\"\/>\n<\/figure>\n\n\n<h2>Korte samenvatting: van plan naar resultaat<\/h2>\n\n<p>Wie kan de <strong>Plan<\/strong> begrijpt, stuurt de prestaties. De fasen \u2018Parsing\u2019, \u2018Preparing\u2019, \u2018Optimizing\u2019 en \u2018Executing\u2019 maken duidelijk waar tijd verloren gaat. Het op tijd gebaseerde kostenmodel vanaf versie 11.0, goed bijgehouden statistieken en histogrammen zorgen ervoor dat schattingen betrouwbaar zijn. EXPLAIN, EXPLAIN ANALYZE en de Optimizer Trace zorgen voor transparantie, die ik vertaal naar concrete maatregelen. Met een strakke indexstrategie, een duidelijk query-ontwerp en de juiste infrastructuur leveren MariaDB-query\u2019s constant snelle antwoorden op.<\/p>","protected":false},"excerpt":{"rendered":"<p>Ontdek hoe de MariaDB Query Optimizer intern werkt, hoe je het SQL-uitvoeringsplan met EXPLAIN analyseert en hoe je praktische database-tuning toepast \u2013 inclusief tips voor performante webapplicaties.<\/p>","protected":false},"author":1,"featured_media":20923,"comment_status":"","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"footnotes":""},"categories":[781],"tags":[],"class_list":["post-20930","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-datenbanken-administration-anleitungen"],"acf":[],"_wp_attached_file":null,"_wp_attachment_metadata":null,"litespeed-optimize-size":null,"litespeed-optimize-set":null,"_elementor_source_image_hash":null,"_wp_attachment_image_alt":null,"stockpack_author_name":null,"stockpack_author_url":null,"stockpack_provider":null,"stockpack_image_url":null,"stockpack_license":null,"stockpack_license_url":null,"stockpack_modification":null,"color":null,"original_id":null,"original_url":null,"original_link":null,"unsplash_location":null,"unsplash_sponsor":null,"unsplash_exif":null,"unsplash_attachment_metadata":null,"_elementor_is_screenshot":null,"surfer_file_name":null,"surfer_file_original_url":null,"envato_tk_source_kit":null,"envato_tk_source_index":null,"envato_tk_manifest":null,"envato_tk_folder_name":null,"envato_tk_builder":null,"envato_elements_download_event":null,"_menu_item_type":null,"_menu_item_menu_item_parent":null,"_menu_item_object_id":null,"_menu_item_object":null,"_menu_item_target":null,"_menu_item_classes":null,"_menu_item_xfn":null,"_menu_item_url":null,"_trp_menu_languages":null,"rank_math_primary_category":null,"rank_math_title":null,"inline_featured_image":null,"_yoast_wpseo_primary_category":null,"rank_math_schema_blogposting":null,"rank_math_schema_videoobject":null,"_oembed_049c719bc4a9f89deaead66a7da9fddc":null,"_oembed_time_049c719bc4a9f89deaead66a7da9fddc":null,"_yoast_wpseo_focuskw":null,"_yoast_wpseo_linkdex":null,"_oembed_27e3473bf8bec795fbeb3a9d38489348":null,"_oembed_c3b0f6959478faf92a1f343d8f96b19e":null,"_trp_translated_slug_en_us":null,"_wp_desired_post_slug":null,"_yoast_wpseo_title":null,"tldname":null,"tldpreis":null,"tldrubrik":null,"tldpolicylink":null,"tldsize":null,"tldregistrierungsdauer":null,"tldtransfer":null,"tldwhoisprivacy":null,"tldregistrarchange":null,"tldregistrantchange":null,"tldwhoisupdate":null,"tldnameserverupdate":null,"tlddeletesofort":null,"tlddeleteexpire":null,"tldumlaute":null,"tldrestore":null,"tldsubcategory":null,"tldbildname":null,"tldbildurl":null,"tldclean":null,"tldcategory":null,"tldpolicy":null,"tldbesonderheiten":null,"tld_bedeutung":null,"_oembed_d167040d816d8f94c072940c8009f5f8":null,"_oembed_b0a0fa59ef14f8870da2c63f2027d064":null,"_oembed_4792fa4dfb2a8f09ab950a73b7f313ba":null,"_oembed_33ceb1fe54a8ab775d9410abf699878d":null,"_oembed_fd7014d14d919b45ec004937c0db9335":null,"_oembed_21a029d076783ec3e8042698c351bd7e":null,"_oembed_be5ea8a0c7b18e658f08cc571a909452":null,"_oembed_a9ca7a298b19f9b48ec5914e010294d2":null,"_oembed_f8db6b27d08a2bb1f920e7647808899a":null,"_oembed_168ebde5096e77d8a89326519af9e022":null,"_oembed_cdb76f1b345b42743edfe25481b6f98f":null,"_oembed_87b0613611ae54e86e8864265404b0a1":null,"_oembed_27aa0e5cf3f1bb4bc416a4641a5ac273":null,"_oembed_time_27aa0e5cf3f1bb4bc416a4641a5ac273":null,"_tldname":null,"_tldclean":null,"_tldpreis":null,"_tldcategory":null,"_tldsubcategory":null,"_tldpolicy":null,"_tldpolicylink":null,"_tldsize":null,"_tldregistrierungsdauer":null,"_tldtransfer":null,"_tldwhoisprivacy":null,"_tldregistrarchange":null,"_tldregistrantchange":null,"_tldwhoisupdate":null,"_tldnameserverupdate":null,"_tlddeletesofort":null,"_tlddeleteexpire":null,"_tldumlaute":null,"_tldrestore":null,"_tldbildname":null,"_tldbildurl":null,"_tld_bedeutung":null,"_tldbesonderheiten":null,"_oembed_ad96e4112edb9f8ffa35731d4098bc6b":null,"_oembed_8357e2b8a2575c74ed5978f262a10126":null,"_oembed_3d5fea5103dd0d22ec5d6a33eff7f863":null,"_eael_widget_elements":null,"_oembed_0d8a206f09633e3d62b95a15a4dd0487":null,"_oembed_time_0d8a206f09633e3d62b95a15a4dd0487":null,"_aioseo_description":null,"_eb_attr":null,"_eb_data_table":null,"_oembed_819a879e7da16dd629cfd15a97334c8a":null,"_oembed_time_819a879e7da16dd629cfd15a97334c8a":null,"_acf_changed":null,"_wpcode_auto_insert":null,"_edit_last":null,"_edit_lock":null,"_oembed_e7b913c6c84084ed9702cb4feb012ddd":null,"_oembed_bfde9e10f59a17b85fc8917fa7edf782":null,"_oembed_time_bfde9e10f59a17b85fc8917fa7edf782":null,"_oembed_03514b67990db061d7c4672de26dc514":null,"_oembed_time_03514b67990db061d7c4672de26dc514":null,"rank_math_news_sitemap_robots":null,"rank_math_robots":null,"_eael_post_view_count":"156","_trp_automatically_translated_slug_ru_ru":null,"_trp_automatically_translated_slug_et":null,"_trp_automatically_translated_slug_lv":null,"_trp_automatically_translated_slug_fr_fr":null,"_trp_automatically_translated_slug_en_us":null,"_wp_old_slug":null,"_trp_automatically_translated_slug_da_dk":null,"_trp_automatically_translated_slug_pl_pl":null,"_trp_automatically_translated_slug_es_es":null,"_trp_automatically_translated_slug_hu_hu":null,"_trp_automatically_translated_slug_fi":null,"_trp_automatically_translated_slug_ja":null,"_trp_automatically_translated_slug_lt_lt":null,"_elementor_edit_mode":null,"_elementor_template_type":null,"_elementor_version":null,"_elementor_pro_version":null,"_wp_page_template":null,"_elementor_page_settings":null,"_elementor_data":null,"_elementor_css":null,"_elementor_conditions":null,"_happyaddons_elements_cache":null,"_oembed_75446120c39305f0da0ccd147f6de9cb":null,"_oembed_time_75446120c39305f0da0ccd147f6de9cb":null,"_oembed_3efb2c3e76a18143e7207993a2a6939a":null,"_oembed_time_3efb2c3e76a18143e7207993a2a6939a":null,"_oembed_59808117857ddf57e478a31d79f76e4d":null,"_oembed_time_59808117857ddf57e478a31d79f76e4d":null,"_oembed_965c5b49aa8d22ce37dfb3bde0268600":null,"_oembed_time_965c5b49aa8d22ce37dfb3bde0268600":null,"_oembed_81002f7ee3604f645db4ebcfd1912acf":null,"_oembed_time_81002f7ee3604f645db4ebcfd1912acf":null,"_elementor_screenshot":null,"_oembed_7ea3429961cf98fa85da9747683af827":null,"_oembed_time_7ea3429961cf98fa85da9747683af827":null,"_elementor_controls_usage":null,"_elementor_page_assets":[],"_elementor_screenshot_failed":null,"theplus_transient_widgets":null,"_eael_custom_js":null,"_wp_old_date":null,"_trp_automatically_translated_slug_it_it":null,"_trp_automatically_translated_slug_pt_pt":null,"_trp_automatically_translated_slug_zh_cn":null,"_trp_automatically_translated_slug_nl_nl":null,"_trp_automatically_translated_slug_pt_br":null,"_trp_automatically_translated_slug_sv_se":null,"rank_math_analytic_object_id":null,"rank_math_internal_links_processed":"1","_trp_automatically_translated_slug_ro_ro":null,"_trp_automatically_translated_slug_sk_sk":null,"_trp_automatically_translated_slug_bg_bg":null,"_trp_automatically_translated_slug_sl_si":null,"litespeed_vpi_list":null,"litespeed_vpi_list_mobile":null,"rank_math_seo_score":null,"rank_math_contentai_score":null,"ilj_limitincominglinks":null,"ilj_maxincominglinks":null,"ilj_limitoutgoinglinks":null,"ilj_maxoutgoinglinks":null,"ilj_limitlinksperparagraph":null,"ilj_linksperparagraph":null,"ilj_blacklistdefinition":null,"ilj_linkdefinition":null,"_eb_reusable_block_ids":null,"rank_math_focus_keyword":"MariaDB Optimizer","rank_math_og_content_image":null,"_yoast_wpseo_metadesc":null,"_yoast_wpseo_content_score":null,"_yoast_wpseo_focuskeywords":null,"_yoast_wpseo_keywordsynonyms":null,"_yoast_wpseo_estimated-reading-time-minutes":null,"rank_math_description":null,"surfer_last_post_update":null,"surfer_last_post_update_direction":null,"surfer_keywords":null,"surfer_location":null,"surfer_draft_id":null,"surfer_permalink_hash":null,"surfer_scrape_ready":null,"_thumbnail_id":"20923","footnotes":null,"_links":{"self":[{"href":"https:\/\/webhosting.de\/nl\/wp-json\/wp\/v2\/posts\/20930","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/webhosting.de\/nl\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/webhosting.de\/nl\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/webhosting.de\/nl\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/webhosting.de\/nl\/wp-json\/wp\/v2\/comments?post=20930"}],"version-history":[{"count":0,"href":"https:\/\/webhosting.de\/nl\/wp-json\/wp\/v2\/posts\/20930\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/webhosting.de\/nl\/wp-json\/wp\/v2\/media\/20923"}],"wp:attachment":[{"href":"https:\/\/webhosting.de\/nl\/wp-json\/wp\/v2\/media?parent=20930"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/webhosting.de\/nl\/wp-json\/wp\/v2\/categories?post=20930"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/webhosting.de\/nl\/wp-json\/wp\/v2\/tags?post=20930"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}