{"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":"en-indgaende-forklaring-af-mariadbs-foresporgselsoptimerer-indsigt-i-sql-tuning","status":"publish","type":"post","link":"https:\/\/webhosting.de\/da\/mariadb-query-optimizer-intern-erklaert-sql-tuning-insight\/","title":{"rendered":"MariaDB Query Optimizer forklaret internt: Grundl\u00e6ggende principper, planer og praksis"},"content":{"rendered":"<p>Jeg forklarer den <strong>MariaDB-optimeringsmodul<\/strong> Fra praksis: hvordan han udarbejder planer, estimerer omkostninger, og hvorfor han nogle gange tager fejl. S\u00e5dan l\u00e6ser du SQL-udf\u00f8relsesplanen m\u00e5lrettet, anvender indekser p\u00e5 en fornuftig m\u00e5de og styrer optimeringsv\u00e6rkt\u00f8jet med fakta i stedet for mavefornemmelse.<\/p>\n\n<h2>Centrale punkter<\/h2>\n\n<p>Til at begynde med vil jeg kort opsummere de vigtigste elementer, s\u00e5 du kan s\u00e6tte de f\u00f8lgende afsnit i den rette sammenh\u00e6ng og <strong>Oversigt<\/strong> beholder.<\/p>\n<ul>\n  <li><strong>Faser<\/strong>: Parsing, forberedelse, optimering og udf\u00f8relse udg\u00f8r livscyklussen for enhver foresp\u00f8rgsel.<\/li>\n  <li><strong>Omkostningsmodel<\/strong>: Tidsbaserede v\u00e6rdier i mikrosekunder styrer indeksvalg, scanninger og r\u00e6kkef\u00f8lgen af sammenkoblinger.<\/li>\n  <li><strong>Statistik<\/strong>: Kardinalitet og histogrammer er afg\u00f8rende for estimeringen af selektivitet.<\/li>\n  <li><strong>Gennemsigtighed<\/strong>: EXPLAIN, EXPLAIN ANALYZE og Optimizer Trace \u00e5bner \u00bbblackboxen\u00ab.<\/li>\n  <li><strong>Indstilling<\/strong>: Indekser, omskrivning af foresp\u00f8rgsler, ANALYZE TABLE og omkostningsparametre \u00f8ger hastigheden.<\/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>En foresp\u00f8rgsels livscyklus i MariaDB<\/h2>\n\n<p>Inden en plan udarbejdes, gennemg\u00e5r en foresp\u00f8rgsel fire faser, som jeg m\u00e5lrettet tjekker i hverdagen for at <strong>\u00c5rsager<\/strong> at finde \u00e5rsagen til langsomheden. Under parsningen omdanner MariaDB SQL til en intern struktur; her bliver syntaksfejl synlige. I forberedelsesfasen gennemg\u00e5r motoren tabeller, kolonner og potentielle indekser og udf\u00f8rer enkle omformninger. Derefter f\u00f8lger optimeringen, hvor mulige planer beregnes og vurderes ved hj\u00e6lp af en omkostningsmodel. Under udf\u00f8relsen implementerer serveren den valgte plan trin for trin: l\u00e6se, sammenf\u00f8je, filtrere, returnere.<\/p>\n\n<p>Jeg opdeler analysefejl tydeligt efter fase, fordi diagnoserne p\u00e5 den m\u00e5de virker hurtigere, og <strong>Foranstaltninger<\/strong> virke m\u00e5lrettet. Oftest skyldes ydeevneproblemer optimeringen: forkerte sk\u00f8n, manglende indekser eller ugunstige r\u00e6kkef\u00f8lger af sammenkoblinger. Parsing-fejl er trivielle, men forberedelsesfasen kan allerede indeholde finesser som opl\u00f8sning af visninger eller omformulering af underforesp\u00f8rgsler. I udf\u00f8relsesfasen bliver ineffektiviteter s\u00e5 uundg\u00e5eligt tydelige, hvis der tidligere er valgt en fuld scanning. Derfor starter jeg enhver unders\u00f8gelse med et struktureret overblik over alle fire faser.<\/p>\n\n<h2>Hvordan optimeringsv\u00e6rkt\u00f8jet tr\u00e6ffer sin beslutning internt<\/h2>\n\n<p>MariaDB arbejder p\u00e5 et omkostningsbaseret grundlag og vurderer alternative l\u00f8sninger ved hj\u00e6lp af en <strong>Omkostningsfunktion<\/strong>. For hver variant estimerer serveren antallet af l\u00e6ste r\u00e6kker, selektiviteten af WHERE\/ON, adgangstyper som f.eks. tabelscanning, indeksscanning og intervalscanning samt tidsforbruget for de enkelte operationer. Internt skelner serveren mellem join_preparation og join_optimization. I join_preparation udf\u00f8res omskrivning af foresp\u00f8rgsler, forenkling af betingelser, omformning af underforesp\u00f8rgsler og opl\u00f8sning af visninger. I `join_optimization` beregnes join-r\u00e6kkef\u00f8lger, indekskandidater kontrolleres via `ref_optimizer_key_uses`, antallet af r\u00e6kker estimeres via r\u00e6kkevidde-scanninger, og betingelser tildeles konkrete tabeller s\u00e5 tidligt som muligt.<\/p>\n\n<p>Denne mekanisme forklarer, hvorfor et lille filter p\u00e5 det forkerte sted kan medf\u00f8re dyre <strong>Konsekvenser<\/strong> har. Hvis \u00bbattaching_conditions_to_tables\u00ab sker for sent, tr\u00e6kker planen un\u00f8dvendigt mange r\u00e6kker med gennem sammenkoblingerne. Hvis statistikkerne er for\u00e6ldede, bliver \u00bbrows_estimation\u00ab og \u00bbSelectivity\u00ab forkerte; optimeringsmodulet v\u00e6lger s\u00e5 adgangsveje, der ser gunstige ud, men i virkeligheden er langsomme. Det er netop disse justeringsmuligheder, jeg fokuserer p\u00e5: bedre statistik, klarere pr\u00e6dikater, p\u00e6nt sorterede sammensatte indekser. Derefter \u00e6ndrer valg af plan sig ofte m\u00e6rkbart.<\/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>Omkostningsmodel fra og med MariaDB 11.0<\/h2>\n\n<p>De nyeste udgivelser vurderer ikke l\u00e6ngere arbejdet groft ud fra v\u00e6gt, men ved hj\u00e6lp af <strong>mikrosekunder<\/strong> til konkrete lagringsoperationer. Parametre som optimizer_disk_read_cost, optimizer_disk_read_ratio og optimizer_where_cost bringer modellen t\u00e6ttere p\u00e5 de faktiske k\u00f8retider. P\u00e5 den m\u00e5de sammenligner optimizeret indeks-range-scan med fuld scanning p\u00e5 baggrund af reelle tidsantagelser. LAST_QUERY_COST viser de estimerede samlede omkostninger og stemmer ofte betydeligt bedre overens med virkeligheden end tidligere. For dataintensive systemer betaler denne finere opdeling sig med det samme.<\/p>\n\n<p>Jeg kalibrerer modellen omhyggeligt, n\u00e5r hardwareegenskaber strider mod standardantagelserne og dermed <strong>Planvalg<\/strong> forvr\u00e6nge. NVMe-SSD\u2019er, distribueret lager eller specielle cacher kan m\u00e6rkbart \u00e6ndre diskforholdet og l\u00e6setiderne. Sm\u00e5 justeringer af optimizer_costs f\u00e5r MariaDB til at foretr\u00e6kke fornuftige stier. Jeg dokumenterer hver \u00e6ndring og tjekker derefter EXPLAIN ANALYZE for at m\u00e5le effekten. Uden m\u00e5linger forbliver tuning et lotteri.<\/p>\n\n<h2>Selektivitet, statistikker og histogrammer<\/h2>\n\n<p>Gode sk\u00f8n starter med pr\u00e6cise <strong>kardinalitet<\/strong> og p\u00e5lidelig selektivitet. MariaDB f\u00f8rer statistik over forskellige v\u00e6rdier for hver kolonne og kan valgfrit anvende histogrammer til fordelinger. Is\u00e6r uj\u00e6vne data \u2013 hotspots, Zipf-fordelinger, s\u00e6sonm\u00f8nstre \u2013 drager fordel af histogrammer. Efter store data\u00e6ndringer udf\u00f8rer jeg ANALYZE TABLE, s\u00e5 optimeringen igen kan basere sig p\u00e5 faktiske data. Glemmer man dette, risikerer man fuldscanninger, der objektivt set er forkerte.<\/p>\n\n<p>Jeg planl\u00e6gger ANALYZE som en regelm\u00e6ssig opgave, tilpasset <strong>\u00c6ndringer<\/strong> i datam\u00e6ngden og p\u00e5 kritiske tabeller. Ved st\u00e6rkt sk\u00e6ve kolonnefordelinger hj\u00e6lper histogrammer med at give et realistisk billede af selektiviteten af singul\u00e6re v\u00e6rdier. Dette reducerer fejlvurderinger ved r\u00e6kkevidde-scanninger og sammenfletningsstrategier. Kombineret med passende sammensatte indekser forbedres tr\u00e6fsikkerheden markant. Resultat: kortere k\u00f8retider og mindre 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 og l\u00e6sning af udf\u00f8relsesplaner<\/h2>\n\n<p>For at g\u00f8re beslutningerne synlige bruger jeg EXPLAIN, EXPLAIN EXTENDED og <strong>FORMAT=JSON<\/strong>. De klassiske kolonner giver et hurtigt overblik: id, select_type, table, type, possible_keys, key, key_len, ref, rows og eventuelt filtered. En type=ALL indikerer en fuld scanning, hvilket sj\u00e6ldent er \u00f8nskeligt. FORMAT=JSON viser detaljeret, hvordan betingelser er blevet flyttet, og hvilke stier optimeringsv\u00e6rkt\u00f8jet har vurderet. I forbindelse med hosting anbefaler jeg vejledningen til <a href=\"https:\/\/webhosting.de\/da\/planer-for-udforelse-af-databaseforesporgsler-hosting-optimering-performance-indsigt\/\">K\u00f8rselsplaner i hosting<\/a>, for at sammenk\u00e6de planoplysninger med infrastrukturvirkninger.<\/p>\n\n<p>For at kunne fortolke resultaterne hurtigt bruger jeg en lille tabel, der kort oversigt over de typiske v\u00e6rdier og dermed <strong>Fejlfortolkninger<\/strong> forhindres.<\/p>\n\n<table>\n  <thead>\n    <tr>\n      <th>EXPLAIN-felt<\/th>\n      <th>Typisk v\u00e6rdi<\/th>\n      <th>Betydning i praksis<\/th>\n    <\/tr>\n  <\/thead>\n  <tbody>\n    <tr>\n      <td>type<\/td>\n      <td>ALL, range, ref, eq_ref, const<\/td>\n      <td>Jo l\u00e6ngere til h\u00f8jre, jo mere selektiv; ALL angiver fuld scanning.<\/td>\n    <\/tr>\n    <tr>\n      <td>possible_keys<\/td>\n      <td>Indeksliste<\/td>\n      <td>Indekser, der teoretisk set passer; mangler der kandidater her, mangler der struktur.<\/td>\n    <\/tr>\n    <tr>\n      <td>n\u00f8gle<\/td>\n      <td>Indeksnavn<\/td>\n      <td>Faktisk anvendt indeks; tomt betyder, at der ikke anvendes noget indeks.<\/td>\n    <\/tr>\n    <tr>\n      <td>r\u00e6kker<\/td>\n      <td>Antal<\/td>\n      <td>Ansl\u00e5et antal l\u00e6ste linjer; afviger kraftigt fra virkeligheden = d\u00e5rlig statistik.<\/td>\n    <\/tr>\n    <tr>\n      <td>filtreret<\/td>\n      <td>Procent<\/td>\n      <td>Hvor meget der sendes videre efter filteret; lavt er ofte godt.<\/td>\n    <\/tr>\n  <\/tbody>\n<\/table>\n\n<h2>Hvorfor optimeringsv\u00e6rkt\u00f8jet nogle gange tager fejl<\/h2>\n\n<p>Ingen omkostningsmodel passer til alle situationer, derfor korrigerer jeg <strong>Fejl<\/strong> m\u00e5lrettet. For\u00e6ldede statistikker f\u00f8rer til forkerte sk\u00f8n over r\u00e6kker og uhensigtsm\u00e6ssige r\u00e6kkef\u00f8lger af sammenk\u00e6dninger. Forkert opbyggede sammensatte indekser forhindrer indeksanvendelse ved filtrering p\u00e5 flere kolonner. Meget indlejrede underforesp\u00f8rgsler vanskeligg\u00f8r effektive omskrivninger og blokerer materialisering. Manglende eller vildledende filtre tvinger motoren til at flytte mange r\u00e6kker, f\u00f8r nyttige pr\u00e6dikater tr\u00e6der i kraft.<\/p>\n\n<p>Jeg tjekker f\u00f8rst, om formuleringen af foresp\u00f8rgslen overholder <strong>Indeks<\/strong> Det, der virkelig hj\u00e6lper: reglen om venstre pr\u00e6fiks, passende sorteringsr\u00e6kkef\u00f8lge, undg\u00e5else af funktioner p\u00e5 kolonner i WHERE-s\u00e6tningen. Derefter tjekker jeg i EXPLAIN ANALYZE, om virkeligheden bekr\u00e6fter estimatet. Hvis ikke, f\u00f8lger ANALYZE TABLE og om n\u00f8dvendigt en omskrivning. F\u00f8rst til sidst bruger jeg FORCE INDEX eller hinting, da det kan begr\u00e6nse fremtidige optimeringer.<\/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>M\u00e5lrettet brug af Optimizer Trace<\/h2>\n\n<p>Hvis EXPLAIN ikke er nok, aktiverer jeg Optimizer Trace og f\u00f8lger med i <strong>Beslutninger<\/strong> i JSON-loggen. Her kan jeg se, hvilke planer der er blevet overvejet, forkastet eller godkendt. Jeg kan se, hvorfor en betingelse tr\u00e6der i kraft for sent, eller hvorfor et indeks ikke kom med i den endelige udv\u00e6lgelse. Loggen viser ogs\u00e5, hvordan betingelserne er blevet omarrangeret. Dette overblik sk\u00e6rper forst\u00e5elsen og giver konkrete redskaber til den n\u00e6ste optimering.<\/p>\n\n<p>Jeg gemmer relevante afsnit af sporet sammen med query-hash og <strong>Parametre<\/strong>. P\u00e5 den m\u00e5de kan jeg senere sammenligne, hvilken \u00e6ndring der havde hvilken effekt. Dokumentationen til MariaDB-serveren og forskellige foredrag i \u00f8kosystemet beskriver felterne udf\u00f8rligt (kilde: MariaDB-serverens dokumentation om Query Optimizer og Optimizer Trace). Med dette v\u00e6rkt\u00f8j finder jeg fejlagtige antagelser hurtigere end ved trial-and-error. Jeg sparer is\u00e6r tid ved komplekse sammenkoblinger.<\/p>\n\n<h2>Praksis: Databasetuning trin for trin<\/h2>\n\n<p>Jeg starter enhver optimering med en klar <strong>M\u00e5ling<\/strong>. Jeg identificerer problemforesp\u00f8rgsler via overv\u00e5gning og det <a href=\"https:\/\/webhosting.de\/da\/mysql-langsom-foresporgselslog-hosting-analyse-queryperf\/\">Langsom foresp\u00f8rgselslog<\/a>. Derefter sammenligner jeg EXPLAIN med EXPLAIN ANALYZE for at sammenligne den planlagte og den faktiske udf\u00f8relse. Jeg tilpasser indeksstrategien til WHERE, JOIN og ORDER BY; sammensatte indekser retter jeg ind efter de hyppigste adgangspunkter. Jeg bruger kun FORCE INDEX, hvis optimeringsv\u00e6rkt\u00f8jet v\u00e6lger den forkerte kandidat p\u00e5 trods af korrekte statistikker.<\/p>\n\n<p>Hvert trin indeb\u00e6rer pleje af <strong>Statistik<\/strong>: ANALYZE TABLE p\u00e5 tabeller med stor aktivitet, histogrammer for sk\u00e6ve fordelinger. Jeg forenkler un\u00f8dvendige underforesp\u00f8rgsler, materialiserer mellemresultater efter behov og rydder op i gamle midlertidige l\u00f8sninger. Ved brug af specialhardware tjekker jeg optimizer_costs, s\u00e5 mikrosekundmodellen holder stik. Jeg dokumenterer hver \u00e6ndring med f\u00f8r- og efter-v\u00e6rdier, s\u00e5 effekten forbliver sporbar p\u00e5 lang sigt.<\/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>Typiske optimeringsproblemer og l\u00f8sninger<\/h2>\n\n<p>Hvis EXPLAIN viser type=ALL, selvom possible_keys er udfyldt, ser jeg f\u00f8rst p\u00e5 <strong>Selektivitet<\/strong>. Ofte passer kolonner\u00e6kkef\u00f8lgen i det sammensatte indeks ikke, eller en funktion forhindrer brugen af indekset. I s\u00e5 fald vender jeg r\u00e6kkef\u00f8lgen, fjerner forstyrrende funktioner eller opdeler pr\u00e6dikater. Ved forkert join-r\u00e6kkef\u00f8lge unders\u00f8ger jeg, om det er muligt at filtrere tidligt, f.eks. ved at placere den mere selektive tabel f\u00f8rst. Subqueries omdanner jeg, hvor det giver mening, til joins eller TEMPORARY-tabeller.<\/p>\n\n<p>Jeg kan ogs\u00e5 genkende forkerte beslutninger p\u00e5 st\u00e6rkt afvigende <strong>r\u00e6kker<\/strong> mellem plan og virkelighed. I s\u00e5 fald kan ANALYZE TABLE eller et histogram for den p\u00e5g\u00e6ldende kolonne v\u00e6re en hj\u00e6lp. Hvis selv korrekte statistikker ikke f\u00f8rer til det \u00f8nskede resultat, overvejer jeg at bruge eksplicitte hints. F\u00f8rst sikrer jeg mig, at der er kontrolm\u00e5linger og m\u00e5lev\u00e6rdier, s\u00e5 senere versioner af optimeringsv\u00e6rkt\u00f8jet ikke bliver bremset af de gemte data. Her betaler det sig at v\u00e6re disciplineret med dokumentationen.<\/p>\n\n<h2>Hosting-sammenh\u00e6ng og driftsm\u00e6ssige aspekter<\/h2>\n\n<p>Foresp\u00f8rgselskvaliteten og infrastrukturen skal passe sammen, ellers g\u00e5r applikationen til spilde <strong>Potentiale<\/strong>. Hurtige SSD\u2019er, konsistente cacher og en velfungerende konfiguration er grundlaget for, at optimeringsv\u00e6rkt\u00f8jet kan tr\u00e6ffe gode beslutninger. H\u00f8j trafik tillader ikke fuldst\u00e6ndige scanninger; blot f\u00e5 d\u00e5rlige foresp\u00f8rgsler kan bremse hele systemer. Til MySQL\/MariaDB-milj\u00f8er i produktiv drift giver praktiske r\u00e5d som <a href=\"https:\/\/webhosting.de\/da\/mysql-optimizer-query-hosting-optimering-serverboost\/\">MySQL-optimeringsv\u00e6rkt\u00f8j<\/a> Nyttige tanker om kombinationen af plan og platform. Hvis man tager h\u00f8jde for dette niveau, kan man forhindre flaskehalse, f\u00f8r de eskalerer.<\/p>\n\n<p>Jeg kombinerer altid plananalyse med n\u00f8gletal vedr\u00f8rende <strong>I\/O<\/strong>, latenstid og parallelitet. Hvis v\u00e6rdierne ikke stemmer overens med den antagne omkostningsmodel, tjekker jeg parametrene. Derefter ser jeg p\u00e5 bufferst\u00f8rrelser, parallelle arbejdsbelastninger og fordelingen af hotsets. Med dette overblik lykkes det at drive foresp\u00f8rgsler og ressourcer harmonisk og holde spidsbelastninger under kontrol.<\/p>\n\n<h2>Join- og adgangsstier i praksis<\/h2>\n\n<p>Jeg afklarer mange misforst\u00e5elser ved at <strong>Adgangstyper<\/strong> afveje dem m\u00e5lrettet mod hinanden. En <em>interval<\/em>- eller <em>ref<\/em>-Adgang lykkes n\u00e6sten altid <em>ALL<\/em>. Ved logiske sammenkoblinger p\u00e5 entydige n\u00f8gler (<em>eq_ref<\/em>) er planerne s\u00e6rligt stabile. Jeg tjekker desuden, om en <strong>D\u00e6kningsindeks<\/strong> der fuldt ud d\u00e6kker foresp\u00f8rgslen: Hvis alle de n\u00f8dvendige kolonner er med i indekset, sparer MariaDB dyre tabelladgange. <strong>Pushdown i indekstilstand (ICP)<\/strong> hj\u00e6lper med at kontrollere yderligere WHERE-betingelser allerede i indekset \u2013 det reducerer antallet af returnerede r\u00e6kker og I\/O.<\/p>\n\n<p>Omkring <strong>Indeksfusion<\/strong> MariaDB kan kombinere flere indekser (sk\u00e6ringsm\u00e6ngde\/union). Det er nyttigt ved OR-pr\u00e6dikater eller flere selektive betingelser, men ofte langsommere end et velvalgt sammensat indeks. Jeg vurderer desuden <strong>MRR<\/strong> (Multi-Range Read) og <strong>BKA<\/strong> (Batched Key Access). MRR sorterer de prim\u00e6rn\u00f8gler, der skal l\u00e6ses, for at udj\u00e6vne tilf\u00e6ldig I\/O; BKA samler join-opslag og giver is\u00e6r fordele ved ikke-overlappende joins. I praksis tester jeg BKA\/MRR via optimizer_switch og kontrollerer med EXPLAIN ANALYZE, om I\/O-m\u00f8nstrene falder. Hvis MariaDB derimod anvender <strong>Blok med indlejret sl\u00f8jfe<\/strong> (BNL) er det som regel mere fordelagtigt at bruge flere join-buffere (join_buffer_size) \u2013 eller en omskrivning, der muligg\u00f8r \u00e6gte indeks-joins.<\/p>\n\n<pre><code>-- Eksempel: Sammensat indeks til sammenf\u00f8jning + filtrering + sortering\nCREATE INDEX ix_orders_cust_status_created\n  ON orders (customer_id, status, created_at);\n\n-- Typisk foresp\u00f8rgsel\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>Med ovenst\u00e5ende indeks kan optimeringsmodulet v\u00e6lge den mest selektive r\u00e6kkef\u00f8lge, udvurdere filtre tidligt og ofte udf\u00f8re sorteringen uden yderligere filsortering.<\/p>\n\n<h2>ORDER BY, GROUP BY, fil-sortering og midlertidige tabeller<\/h2>\n\n<p>Sortering og sammenfatning tager tid. Jeg s\u00f8rger for, at <strong>ORDER BY<\/strong> og <strong>GROUP BY<\/strong> kan k\u00f8re i indeksr\u00e6kkef\u00f8lgen. Det fungerer, hvis pr\u00e6fikset og retningen passer n\u00f8jagtigt. Ellers tr\u00e6der en <strong>Filsortering<\/strong> med sorteringsbuffer (sort_buffer_size) og eventuelt en midlertidig tabel. Hvis resultats\u00e6ttet indeholder brede TEXT-\/BLOB-kolonner, m\u00e6rkes MariaDB hurtigere <em>p\u00e5 disk<\/em> TEMP-tabeller (Aria). Jeg forebygger dette ved kun at v\u00e6lge de kolonner, jeg har brug for, ved f\u00f8rst at indl\u00e6se store felter til sidst eller ved at bruge pr\u00e6fikser med begr\u00e6nset l\u00e6ngde.<\/p>\n\n<p>N\u00e5r jeg laver aggregeringer, bruger jeg, hvor det er muligt, <strong>L\u00f8s indeksscanning<\/strong> (f.eks. GROUP BY p\u00e5 den prim\u00e6re indeksdel) og v\u00e6lg sammensatte indekser i overensstemmelse med grupperingen. N\u00e5r mellemresultaterne bliver store, skalerer en materialisering med fornuftige n\u00f8gler bedre end en enkelt mega-sammenf\u00f8jning. Jeg m\u00e5ler regelm\u00e6ssigt handler-metrikker og Created_tmp_*-t\u00e6llere for at afd\u00e6kke hotspots i sortering og midlertidige tabeller.<\/p>\n\n<h2>Underforesp\u00f8rgsler, semi-join og materialisering<\/h2>\n\n<p>Mange underforesp\u00f8rgsler kan omformuleres effektivt i forberedelsesfasen. IN\/EXISTS-konstruktioner kan bruges som <strong>Semi-Join<\/strong> k\u00f8rer med strategier som materialisering eller LooseScan. Jeg tjekker, om optimeringsprogrammet er et <strong>afledt_sammenfletning<\/strong> kunne udf\u00f8re: Hvis en afledt tabel (eller en WITH-CTE) inds\u00e6ttes i den ydre plan, er dens indekser umiddelbart tilg\u00e6ngelige. Lykkes det ikke, havner underforesp\u00f8rgslen i en midlertidig tabel \u2013 jeg giver den s\u00e5, hvis det er muligt, en n\u00f8gle (f.eks. ved hj\u00e6lp af SELECT DISTINCT\/ORDER BY p\u00e5 n\u00f8glekolonner), s\u00e5 sammenk\u00e6dninger ikke ender i intetheden.<\/p>\n\n<pre><code>-- Eksempel: EXISTS i stedet for IN og en afledt tabel, der kan bruges i en merge-s\u00e6tning\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-- Afledning med entydige n\u00f8gler\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>Jeg bruger EXPLAIN FORMAT=JSON til at kontrollere, om <strong>materialiseret<\/strong> eller <strong>afh\u00e6ngig underforesp\u00f8rgsel<\/strong> blev valgt, og om der er betingelser (<strong>condition pushdown<\/strong>) handle i god tid.<\/p>\n\n<h2>Partitionering og besk\u00e6ring<\/h2>\n\n<p>Partitionering er ikke et alternativ til indekser, men kan <strong>Datam\u00e6ngde pr. adgang<\/strong> reducere drastisk. Optimizeren foretager kun en korrekt besk\u00e6ring, hvis pr\u00e6dikatet opfylder <strong>Partitionsn\u00f8gle<\/strong> rammer entydigt og ikke forvanskes af funktioner. Derfor undg\u00e5r jeg udtryk som DATE(created_at) i WHERE-s\u00e6tningen p\u00e5 partitionerede tabeller og arbejder i stedet med intervalgr\u00e6nser. EXPLAIN viser, hvilke partitioner der l\u00e6ses; brede intervaller tyder p\u00e5 d\u00e5rlig pruning.<\/p>\n\n<p>For mange sm\u00e5 partitioner \u00f8ger planl\u00e6gningsomkostningerne. Derfor v\u00e6lger jeg en fornuftig granularitet (f.eks. m\u00e5nedligt i stedet for dagligt), holder statistikkerne opdaterede for hver partition (ANALYZE PARTITION) og kontrollerer, om vigtige indekser findes lokalt i partitionerne. I migrationsprojekter tager jeg h\u00f8jde for indvirkningen p\u00e5 replikering og backup \u2013 begge dele p\u00e5virker, hvor aggressivt jeg partitionerer.<\/p>\n\n<h2>Sargability og rewrite-m\u00f8nstre<\/h2>\n\n<p>Den enkleste l\u00f8sning er stadig <strong>Sargability<\/strong> \u2013 Betingelser, der g\u00f8r det muligt at bruge indekser. Jeg undg\u00e5r funktioner p\u00e5 kolonner i WHERE-s\u00e6tningen, reducerer konstante udtryk p\u00e5 kolonnens side og opdeler OR-betingelser efter behov i <strong>UNION ALL<\/strong>. Til LIKE-s\u00f8gninger uden indledende anker (\"%foo\") er et BTREE-indeks ikke egnet; her planl\u00e6gger jeg at bruge fuldteksts\u00f8gning eller en passende s\u00f8getjeneste. Til beregninger bruger jeg <strong>indekserede genererede kolonner<\/strong>, s\u00e5 optimeringsv\u00e6rkt\u00f8jet kan genfinde logikken i indekset.<\/p>\n\n<pre><code>-- Anti-m\u00f8nster: Funktion p\u00e5 kolonne\nWHERE DATE(created_at) = '2026-08-01'\n-- Bedre: Interval baseret p\u00e5 r\u00e5v\u00e6rdi\nWHERE created_at &gt;= '2026-08-01' AND created_at &lt; &#039;2026-08-02&#039;\n\n-- Anti-m\u00f8nster: OR forhindrer indeksering\nWHERE status = &#039;open&#039; OR customer_id = 42\n-- Bedre: to s\u00f8gninger med UNION ALL og hver med sit eget indeks\n(SELECT ... WHERE status = &#039;open&#039;)\nUNION ALL\n(SELECT ... WHERE customer_id = 42&#039;);\n<\/code><\/pre>\n\n<p>N\u00e5r det g\u00e6lder sammensatte indekser, mener jeg, at <strong>reglen om venstre pr\u00e6fiks<\/strong> Overhold dette n\u00f8je: Sorter kolonnerne efter selektivitet og efter den sortering, der senere skal bruges. Hvis jeg har brug for en faldende ORDER BY, tager jeg h\u00f8jde for det i indeksopbygningen \u2013 p\u00e5 den m\u00e5de undg\u00e5r jeg en fil-sortering.<\/p>\n\n<h2>Optimizer-kontakt og finjustering af omkostninger<\/h2>\n\n<p>Inden jeg begynder at se p\u00e5 foresp\u00f8rgsler, tjekker jeg <strong>optimizer_switch<\/strong> og bufferhukommelse. Funktioner som <em>mrr<\/em>, <em>batched_key_access<\/em>, <em>index_merge<\/em>, <em>semijoin<\/em>, <em>afledt_sammenfletning<\/em> eller <em>condition_pushdown_for_afledt<\/em> kan tilpasses pr. session. Jeg aktiverer kandidater m\u00e5lrettet til en testsession, m\u00e5ler med EXPLAIN ANALYZE og ruller tilbage, hvis der ikke opn\u00e5s nogen effekt. Join-stien drager fordel af tilstr\u00e6kkelig <strong>join_buffer_size<\/strong>; store sorter af <strong>sort_buffer_size<\/strong>. Samtidig holder jeg \u00f8je med bufferne i forhold til samtidigheden, s\u00e5 serveren ikke begynder at swappe under parallel belastning.<\/p>\n\n<p>P\u00e5 omkostningsniveau justerer jeg, hvis det er n\u00f8dvendigt, de allerede n\u00e6vnte <strong>optimizer_costs<\/strong> i mikrosekunder. Min fremgangsm\u00e5de: sm\u00e5, reversible trin med dokumenterede m\u00e5lepunkter. Jeg bruger <strong>LAST_QUERY_COST<\/strong> til at kontrollere rimeligheden og gentage m\u00e5linger med realistiske parameterv\u00e6rdier, da planer i h\u00f8j grad kan afh\u00e6nge af konkrete v\u00e6rdier.<\/p>\n\n<h2>Planstabilitet, regressioner og team-workflow<\/h2>\n\n<p>Selv en god plan kan blive p\u00e5virket af stigende datam\u00e6ngder eller versionsskift <strong>vippe<\/strong>. Derfor sikrer jeg mig viden om planl\u00e6gningen: Query-hashes, EXPLAIN-JSON, uddrag af optimizer-traces og EXPLAIN ANALYZE-k\u00f8retider. \u00c6ndringer af indekser og omskrivninger foreg\u00e5r hos mig som pull requests med f\u00f8r-og-efter-dokumentation. I CI\/CD-milj\u00f8er tester jeg automatisk kritiske foresp\u00f8rgsler mod repr\u00e6sentative datatilstande. P\u00e5 den m\u00e5de opdager jeg <strong>Regressionsplaner<\/strong> tidligt.<\/p>\n\n<p>I vanskelige tilf\u00e6lde mener jeg, at <strong>Tips<\/strong> (FORCE INDEX, STRAIGHT_JOIN, optimizer_switch pr. foresp\u00f8rgsel) er tilg\u00e6ngelige som en sidste udvej, men brug dem sparsomt og med en udl\u00f8bsdato. Det er bedre at l\u00f8se \u00e5rsagerne \u2013 statistik, indekser, formulering. I teams sikrer en letforst\u00e5elig vejledning om skalerbarhed, indeksdesign og m\u00e5ledisciplin, at nye funktioner ikke ubem\u00e6rket medf\u00f8rer ydeevneproblemer.<\/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>Kort oversigt: Fra plan til resultat<\/h2>\n\n<p>Hvem kan bruge <strong>Planl\u00e6g<\/strong> Forst\u00e5else styrer ydeevnen. Faser som parsing, forberedelse, optimering og udf\u00f8relse forklarer, hvor tiden g\u00e5r tabt. Den tidsbaserede omkostningsmodel fra version 11.0, opdaterede statistikker og histogrammer g\u00f8r estimaterne p\u00e5lidelige. EXPLAIN, EXPLAIN ANALYZE og Optimizer Trace skaber gennemsigtighed, som jeg oms\u00e6tter til konkrete tiltag. Med en velfungerende indeksstrategi, et klart foresp\u00f8rgselsdesign og en passende infrastruktur leverer MariaDB-foresp\u00f8rgsler konstant hurtige svar.<\/p>","protected":false},"excerpt":{"rendered":"<p>L\u00e6r, hvordan MariaDB Query Optimizer fungerer internt, hvordan du analyserer SQL-udf\u00f8relsesplanen med EXPLAIN, og hvordan du gennemf\u00f8rer praktisk databaseoptimering \u2013 inklusive tips til h\u00f8jtydende webapplikationer.<\/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":"130","_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\/da\/wp-json\/wp\/v2\/posts\/20930","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/webhosting.de\/da\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/webhosting.de\/da\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/webhosting.de\/da\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/webhosting.de\/da\/wp-json\/wp\/v2\/comments?post=20930"}],"version-history":[{"count":0,"href":"https:\/\/webhosting.de\/da\/wp-json\/wp\/v2\/posts\/20930\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/webhosting.de\/da\/wp-json\/wp\/v2\/media\/20923"}],"wp:attachment":[{"href":"https:\/\/webhosting.de\/da\/wp-json\/wp\/v2\/media?parent=20930"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/webhosting.de\/da\/wp-json\/wp\/v2\/categories?post=20930"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/webhosting.de\/da\/wp-json\/wp\/v2\/tags?post=20930"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}