"De database is traag" is een van de meest overbelaste zinnen in elk WordPress performance-gesprek. Het kan betekenen dat één enkele query 4 seconden duurt, of dat duizenden snelle queries samen op 4 seconden uitkomen, of dat er een buffer pool ter grootte van een postzegel een dataset van 12 GB moet bedienen. Stuk voor stuk andere problemen, met andere oplossingen. Dit artikel bestaat zodat de rest van de performance-kennisbank de term precies kan gebruiken.
Wat een trage WordPress-database eigenlijk is
Een trage database in WordPress is de situatie waarin het SQL-werk dat nodig is om een pagina (of een admin-actie) op te bouwen de grootste kostenpost van het verzoek wordt. WordPress core, plugins en het thema praten allemaal met MySQL of MariaDB via de $wpdb global, en één ongecachte pagina kan zomaar tussen de 30 en enkele honderden queries draaien voordat er ook maar één byte HTML wordt verstuurd.
Als dat werk de boel gaat domineren, is het bijna altijd één van deze vijf dingen:
- Latency per query. Eén specifieke query duurt te lang. Meestal een ontbrekende index op
wp_postmeta,wp_optionsof een eigen plugin-tabel, of eenLIKE '%term%'over miljoenen rijen. De klassieke diagnose is MySQL's slow query log, aangezet metslow_query_log = 1en eenlong_query_time-drempel (vaak op0.5of zelfs0.1seconde gezet om dit soort dingen op te jagen), en daarna gelezen metEXPLAINom te zien of MySQL een index koos of toch een full table scan deed. MariaDB heeft hetzelfde en gedraagt zich vergelijkbaar. - Het volume aan queries (N+1). Elke query op zich is snel, maar de pagina draait er honderden of duizenden. Een plugin loopt door 200 posts heen en doet per post een
get_post_meta(), en elke iteratie vuurt zijn eigenSELECTaf. In de query log ziet elke regel er gezond uit, maar de wandklok zegt iets heel anders. Dit is verreweg de meest voorkomende oorzaak op een gemiddeld grote site. - Autoload bloat in
wp_options. Elk WordPress-verzoek laadt elke rij uitwp_optionswaarautoloadopyesstaat viawp_load_alloptions(). Op een schone install valt dat in het niets. Na jaren plugin-komen-en-gaan, achtergebleven licentiesleutels en geserialiseerde rommel kan autoload uitgroeien tot meerdere megabytes (zie WordPress autoload-data in wp_options voor het dedicated artikel) die bij elk verzoek opnieuw worden uitgeserialiseerd. WordPress 6.6 heeft daar bewust een Site Health-check voor toegevoegd die autoload boven de 800 KB als kritiek markeert. - Een te kleine InnoDB buffer pool. InnoDB is de storage engine die WordPress standaard gebruikt, en de InnoDB buffer pool is de in-memory cache van tabellen en indexen. Zodra de werkset groter is dan de pool moet MySQL telkens van schijf lezen, en dat is een orde van grootte trager dan uit RAM. Een WooCommerce-database van 12 GB met een buffer pool van 256 MB voelt traag, hoe netjes je queries ook zijn.
- Lange transacties die de rest blokkeren. Een bulk-import, een trage plugin-migratie of een slecht geschreven plugin kan een row- of table-lock zo lang vasthouden dat alle andere verzoeken erachter staan te wachten. Per query lijkt dat allemaal snel. Op de wandklok stond de hele rij stil te wachten.
Deze vijf mechanismen produceren vergelijkbare symptomen (hoge TTFB, trage admin, pieken in load) maar vragen om verschillende oplossingen. Ze door elkaar halen is precies de reden dat performance-werk vaak nergens toe leidt.
Waarom WordPress zo zwaar leunt op de database
WordPress is een database-gedreven CMS, en dat is geen toeval. Posts, pagina's, gebruikers, opties, taxonomieën, comments, custom fields, plugin-instellingen en WooCommerce-orders zitten allemaal in hetzelfde MySQL-schema. Wat aan de voorkant een "pagina" heet, is in de praktijk een query op wp_posts, een join met wp_term_relationships, een fan-out naar wp_postmeta voor de custom fields, een lookup op wp_users voor de auteur, en het laden van elke autoloaded option uit wp_options. En dan moet de theme-template nog beginnen.
Dat ontwerp is precies wat WordPress zo uitbreidbaar maakt. Elke plugin kan data toevoegen aan bestaande tabellen (wp_postmeta, wp_usermeta, wp_options) zonder dat er een schema-migratie aan te pas komt, en elke plugin mag die data op elk verzoek weer uitlezen. De prijs is dat de database in het hete pad zit van elke ongecachte pagina, en dat elke plugin-bouwer dus de macht heeft om willekeurige queries in dat hete pad te gooien. De meesten gaan er voorzichtig mee om. Sommigen gewoon niet. Een plugin die bij elke front-end request een ongeïndexeerde SELECT over wp_postmeta doet is gewoon legale WordPress, en de database is de laag die de rekening krijgt.
Wat de vorm van de traagheid je vertelt
De vorm waarin de traagheid zich aandient verraadt vaak om welk mechanisme het gaat:
- Eén specifieke pagina is traag, de rest doet het prima. Per-query latency of N+1 binnen wat er op die ene pagina draait. Zet de slow query log aan met een lage
long_query_time, hit de pagina één keer, lees de log. - De hele admin is traag terwijl de voorkant gecached en snel is. Vrijwel zeker autoload bloat of een N+1 in de admin-code van een plugin. Het artikel over een trage WordPress admin gaat dieper op het autoload-mechanisme in.
- Traag onder load, snel als je hem alleen test. Of de buffer pool is te klein voor de werkset onder concurrency, of een zware request houdt row-locks vast die andere requests blokkeren. Kijk in
SHOW ENGINE INNODB STATUSvoor lock waits. - Traag op de WooCommerce-admin, nergens anders. De orderlijst, rapportages en voorraadschermen draaien zware joins op
wp_postmeta(of de HPOS order-tabellen). Veel orders, slechte indexen, en N+1 lookups in extensies vormen samen het standaard recept. Het artikel over een trage WooCommerce gaat hier verder op in. - Eerste request na een rustige periode is traag, de rest gaat weer prima. De buffer pool is afgekoeld en InnoDB moest hem weer vanaf disk vullen. Verhoog
innodb_buffer_pool_sizezodat de werkset er ook echt in past.
Query Monitor op een staging-kopie is veruit de snelste manier om te zien welke queries een pagina daadwerkelijk afvuurt en welke plugin ze veroorzaakt. Draai hem op de trage pagina, sorteer op duur, en de boosdoener wijst zich meestal binnen dertig seconden aan.
Wat een trage WordPress-database NIET is
Hier loopt het meeste performance-werk vast. Een paar aangrenzende problemen krijgen ten onrechte de schuld van de database, en dat kost uren werk in de verkeerde hoek.
- Niet "die ene trage query". Eén query van 400 ms is zelden in zijn eentje het probleem. Of hij maakt deel uit van een N+1 die hem 200 keer afvuurt, of hij heeft een ontbrekende index waardoor een lookup van 5 ms een scan van 400 ms wordt. De fix zit in de structurele oorzaak, niet in die ene regel uit de query log. Er een cache overheen plempen verbergt het N+1-probleem alleen maar.
- Niet "de databaseserver is onbereikbaar". Een site die "Error establishing a database connection" geeft heeft geen trage database. Die heeft een database waar het PHP-proces helemaal niet bij kan. Dat is een andere fout met andere oorzaken (verkeerde credentials, MySQL down, max_connections vol, netwerk eruit) en wordt behandeld in het artikel over error establishing database connection.
- Niet "hoge CPU op de databaseserver". CPU is bijna altijd een symptoom van database-traagheid, geen oorzaak. Een query die een full table scan doet brandt CPU op omdat MySQL elke rij moet lezen en vergelijken. Voeg de juiste index toe en zowel de latency als het CPU-gebruik dalen. Meer cores op MySQL gooien terwijl de queries nog steeds rijen scannen lost helemaal niets op.
- Niet hetzelfde als "hoge TTFB". TTFB is de wandkloktijd voordat de eerste byte arriveert. Database-tijd is één input voor TTFB, niet de enige. Een site met een TTFB van 1,5 seconde en een totale querytijd van 50 ms heeft een ander probleem dan een site met een TTFB van 1,5 seconde en een totale querytijd van 1,4 seconde. Meet de bijdrage van de database voordat je hem aanwijst.
- Niet op te lossen met "meer RAM" alleen. RAM bijgooien helpt alleen als de bottleneck een te kleine buffer pool was. Zat de echte bottleneck in een ontbrekende index, een N+1, een autoload-rij van 4 MB of een langlopende lock, dan verandert er met meer RAM niks. "Een grotere server" is de duurste niet-oplossing in het hele WordPress-ecosysteem.
- Niet hetzelfde als uitgeputte PHP-workers. Als alle PHP-FPM-workers staan te wachten op de database, zie je van buitenaf requests in de wachtrij staan en de TTFB onder load oplopen. Dat lijkt op een database-probleem, maar het kan net zo goed een te kleine worker pool zijn voor het soort verzoeken dat de site krijgt. Het artikel over PHP workers legt het verschil uit.
Waar je daarna verder kunt lezen
Als de database vooral in de admin pijn doet, legt het artikel over de trage WordPress admin uit waarom admin-requests de database harder belasten dan front-end-requests. Gaat het om de tijd voordat de eerste byte binnenkomt, dan splitst het artikel over hoge TTFB op wat TTFB precies meet en hoeveel daarvan op het conto van de database komt. Treedt het probleem alleen op onder gelijktijdige load, dan beschrijft het artikel over PHP workers hoe het worker-poolmodel een wachtrij oplevert bovenop trage queries. Draait het om een WooCommerce-shop, dan staat in het artikel over een trage WooCommerce waarom shops zwaarder op de database leunen dan content-sites. Is de database te groot geworden door jarenlang opgehoopte revisies, verweesde postmeta en verlopen transienten, dan loopt de database-opschoongids door proactief onderhoud.