Hoe werkt WP_Query onder de motorkap?

Ontdek hoe WP_Query intern werkt, van SQL-generatie tot caching. Leer je queries te optimaliseren voor een snellere WordPress-site.

8 april 20268 min leestijdDoor We Develop Communication

WP_Query is het kloppende hart van WordPress. Elke pagina die je bezoekt, elk archief dat je bekijkt en elke widget met recente berichten - ze draaien allemaal op dezelfde engine. Maar wat gebeurt er precies als je new WP_Query() aanroept? En waarom is het begrijpen van die interne werking cruciaal voor de performance van je site?

In dit artikel duiken we diep in de mechanismen achter WP_Query. Je leert hoe parameters worden omgezet naar SQL, waar caching een rol speelt en hoe je veelgemaakte fouten vermijdt die je site onnodig vertragen.

Waar past WP_Query in het WordPress-request?

In ons eerdere artikel over wat er gebeurt tijdens een WordPress-request beschreven we het volledige traject van DNS tot HTML-output. WP_Query speelt daarin een centrale rol: het is het moment waarop WordPress bepaalt welke content uit de database wordt opgehaald.

Elke pagina op je WordPress-site begint met een hoofdquery (de "main query"). WordPress analyseert de URL, vertaalt die naar queryparameters en voert vervolgens WP_Query uit. Dit gebeurt automatisch - nog voordat je thema ook maar één regel code uitvoert.

Daarnaast kun je in je thema of plugins secundaire queries aanmaken. Denk aan een sidebar met populaire berichten of een sectie met gerelateerde artikelen. Elke extra query is een extra database-aanroep, en dat telt op.

Van parameters naar SQL: de query-opbouw

De kracht van WP_Query zit in de flexibiliteit van de parameters. Je geeft een array mee met wat je wilt ophalen, en WordPress bouwt daar de juiste SQL-query voor.

Stap 1: Parameters normaliseren

Wanneer je een nieuwe WP_Query aanmaakt, doorloopt WordPress eerst een normaliseringsfase. Standaardwaarden worden ingevuld, aliassen worden vertaald en conflicterende parameters worden opgelost. Zo wordt 'cat' => 5 intern omgezet naar de juiste taxonomy query.

Stap 2: SQL-clausules genereren

WordPress bouwt de SQL-query op uit losse clausules:

  • SELECT - welke kolommen worden opgehaald (standaard alle velden uit wp_posts)
  • JOIN - koppelingen met andere tabellen, zoals wp_postmeta of wp_term_relationships
  • WHERE - filtervoorwaarden op basis van je parameters
  • ORDER BY - sortering, standaard op publicatiedatum
  • LIMIT - het aantal resultaten per pagina

Elke clausule wordt apart opgebouwd en kan via filters worden aangepast. Dat is krachtig, maar ook een risico als plugins ongecontroleerd extra JOINs toevoegen.

Stap 3: De query uitvoeren

De volledige SQL-string gaat naar $wpdb->get_results(). MySQL voert de query uit, en de ruwe resultaten komen terug als een array van objecten. WordPress zet deze vervolgens om naar WP_Post-objecten die je in je template kunt gebruiken.

De rol van caching in WP_Query

WP_Query werkt nauw samen met het WordPress cachingsysteem. Er zijn meerdere lagen die de performance beïnvloeden.

Object cache

Na het ophalen van posts slaat WordPress ze op in de object cache. Bij een volgende aanvraag voor dezelfde post hoeft geen nieuwe databasequery te worden uitgevoerd. Heb je Redis als object cache ingesteld? Dan blijven deze gecachte objecten beschikbaar over meerdere pagina-aanvragen heen.

De object cache slaat niet de volledige query op, maar de individuele posts en hun metadata. Dat betekent dat twee verschillende queries die dezelfde posts opvragen, wel profiteren van de cache voor de losse post-objecten.

Metadata caching

Standaard haalt WP_Query na de hoofdquery ook alle metadata op voor de gevonden posts. Dit heet "metadata priming" en voorkomt dat er per post een extra query nodig is voor custom fields. Het is een slimme optimalisatie, maar bij grote resultatensets kan het ook flink wat geheugen kosten.

Query cache op databaseniveau

MySQL heeft een eigen query cache (al is deze in MySQL 8.0 verwijderd). Voor WordPress-sites op MariaDB kan de query cache nog steeds een rol spelen. Meer hierover lees je in ons artikel over database-optimalisatie voor WordPress.

Belangrijke hooks en filters

WP_Query biedt tientallen hooks waarmee je het gedrag kunt aanpassen. Dit zijn de belangrijkste:

pre_get_posts

Dit is dé manier om de hoofdquery aan te passen. De hook wordt uitgevoerd voordat de SQL wordt opgebouwd, zodat je parameters kunt wijzigen zonder een extra query te veroorzaken.

add_action('pre_get_posts', function($query) {
    if ($query->is_main_query() && $query->is_home()) {
        $query->set('posts_per_page', 12);
    }
});

Let op: controleer altijd of je met de hoofdquery werkt (is_main_query()). Anders beïnvloed je per ongeluk ook sidebar-queries en andere secundaire queries.

posts_clauses

Met deze filter kun je de afzonderlijke SQL-clausules aanpassen. Je krijgt een array met where, join, orderby en andere onderdelen, die je kunt wijzigen voordat de query wordt uitgevoerd.

add_filter('posts_clauses', function($clauses, $query) {
    if ($query->get('custom_sort')) {
        $clauses['orderby'] = 'wp_posts.comment_count DESC';
    }
    return $clauses;
}, 10, 2);

posts_results en the_posts

Na het uitvoeren van de query kun je de resultaten nog aanpassen via posts_results (ruwe resultaten) of the_posts (na caching en filtering). Handig voor het toevoegen van berekende velden of het filteren van resultaten op basis van complexe logica.

Veelgemaakte fouten die performance kosten

Niet elke WP_Query is gelijk. Sommige patronen zien er onschuldig uit maar veroorzaken ernstige performanceproblemen.

Meta queries zonder index

Een meta_query op een niet-geïndexeerd custom field dwingt MySQL tot een full table scan op wp_postmeta. Bij duizenden posts wordt dit een bottleneck. Zorg dat je de juiste indexen hebt op veelgebruikte meta_keys.

// Traag zonder index op 'prijs'
$query = new WP_Query([
    'meta_key'     => 'prijs',
    'meta_value'   => 100,
    'meta_compare' => '>=',
    'meta_type'    => 'NUMERIC',
]);

posts_per_page op -1

Het ophalen van alle posts ('posts_per_page' => -1) is een veelgemaakte fout. Bij honderden of duizenden berichten laad je alles in het geheugen. Gebruik altijd een redelijke limiet en implementeer paginering.

Onnodige velden ophalen

Standaard haalt WP_Query alle kolommen op uit wp_posts. Als je alleen ID's nodig hebt - bijvoorbeeld voor een telling of een lijst met links - gebruik dan 'fields' => 'ids'. Dit scheelt aanzienlijk in geheugengebruik en verwerkingstijd.

// Alleen ID's ophalen - veel lichter
$query = new WP_Query([
    'post_type'      => 'product',
    'posts_per_page' => 50,
    'fields'         => 'ids',
]);

no_found_rows vergeten

Standaard voert WP_Query een extra SQL_CALC_FOUND_ROWS-query uit om het totaal aantal resultaten te bepalen (voor paginering). Als je geen paginering nodig hebt, zet dan 'no_found_rows' => true. Dit kan de querytijd met 20-50% verlagen op grote tabellen.

De hoofdquery debuggen

Wil je weten welke query WordPress uitvoert? Er zijn verschillende manieren om dit te achterhalen.

Query Monitor plugin

Query Monitor is de gouden standaard voor het debuggen van WordPress-queries. Het toont je exact welke SQL-queries worden uitgevoerd, hoe lang ze duren en welke component ze veroorzaakt. In ons artikel over WordPress performance bottlenecks herkennen bespreken we deze tool uitgebreid.

SAVEQUERIES constante

Door define('SAVEQUERIES', true) in je wp-config.php te zetten, slaat WordPress alle queries op met hun doorlooptijd en de aanroepende functie. Handig voor debugging, maar zet dit nooit aan op productie - het kost geheugen en vertraagt je site.

De gegenereerde SQL bekijken

Na het uitvoeren van een WP_Query kun je de gegenereerde SQL opvragen via $query->request. Dit geeft je de volledige SQL-string die naar MySQL is gestuurd.

$query = new WP_Query(['post_type' => 'product', 'posts_per_page' => 10]);
error_log($query->request);

Optimalisatietips voor snellere queries

Met de kennis van hoe WP_Query intern werkt, kun je gericht optimaliseren.

Gebruik transients voor zware queries

Queries die complexe JOINs of meta_queries bevatten en waarvan de resultaten niet elke seconde hoeven te veranderen, kun je cachen met de Transients API. Sla het resultaat op en hergebruik het voor een bepaalde periode.

$results = get_transient('populaire_producten');
if (false === $results) {
    $query = new WP_Query([
        'post_type'      => 'product',
        'posts_per_page' => 10,
        'meta_key'       => 'verkopen',
        'orderby'        => 'meta_value_num',
        'order'          => 'DESC',
    ]);
    $results = $query->posts;
    set_transient('populaire_producten', $results, HOUR_IN_SECONDS);
}

Vermijd geneste queries

Een WP_Query binnen een loop van een andere WP_Query (de "query in een query") is een veelvoorkomend anti-pattern. Het aantal database-aanroepen groeit exponentieel. Haal in plaats daarvan alle benodigde data in één query op en verwerk de resultaten in PHP.

Beperk het aantal JOINs

Elke tax_query of meta_query voegt een JOIN toe aan de SQL. Combineer waar mogelijk, of overweeg om veelgebruikte filterwaarden als taxonomy in te richten in plaats van als postmeta. Taxonomieën zijn geoptimaliseerd voor filtering, postmeta niet.

Controleer autoloaded options

Sommige plugins slaan queryresultaten op als autoloaded options, wat de wp_options-tabel kan opblazen. Houd dit in de gaten, vooral bij caching-plugins die agressief data opslaan.

WP_Query vs. directe SQL

Soms is WP_Query niet de beste keuze. Voor rapportages, bulk-operaties of zeer specifieke queries kan een directe $wpdb-query sneller en efficiënter zijn. Het nadeel is dat je de WordPress-cachelagen en hooks overslaat.

Vuistregel: gebruik WP_Query voor alles wat met content te maken heeft en waar je de WordPress-infrastructuur (caching, filters, plugins) nodig hebt. Gebruik $wpdb alleen voor specifieke operaties waar performance kritiek is en je precies weet wat je doet.

Veelgestelde vragen

Wat is het verschil tussen WP_Query en get_posts()?

get_posts() is een wrapper rond WP_Query met andere standaardwaarden. Het zet bijvoorbeeld suppress_filters op true en no_found_rows op true. Voor eenvoudige lijsten is get_posts() prima, maar voor complexe queries met paginering gebruik je beter WP_Query rechtstreeks.

Hoeveel WP_Query-aanroepen zijn normaal per pagina?

Een gemiddelde WordPress-pagina heeft 5 tot 15 WP_Query-aanroepen. De hoofdquery telt mee, plus sidebar-widgets, gerelateerde berichten en menu's. Meer dan 30 queries per pagina wijst op optimalisatieproblemen.

Kan WP_Query gegevens uit de object cache halen?

Ja, WP_Query maakt gebruik van de WordPress object cache voor individuele posts en hun metadata. Als je Redis of Memcached hebt ingesteld, worden herhaalde queries aanzienlijk sneller afgehandeld doordat de data uit het geheugen komt in plaats van de database.

Wat doet pre_get_posts precies?

pre_get_posts is een action hook die wordt uitgevoerd vlak voordat WP_Query de SQL-query opbouwt. Hiermee kun je de queryparameters aanpassen zonder een extra query uit te voeren. Dit is de aanbevolen manier om de hoofdquery van WordPress te wijzigen.

Veelgestelde vragen

Klaar om digitaal te groeien?

Wij helpen Nederlandse bedrijven met webtechnologie en SEO-strategieën die écht werken. Neem vrijblijvend contact op.