HomeMethod

Knowledge

Web VitalsTechniquesSnippetsAnswersImpactsBottlenecks

Database Query Cache

Cache the results of expensive or repeated database queries to avoid re-executing them on every request.

Overview

Database query caching stores the result of an expensive or frequently repeated SQL query so subsequent requests can retrieve the data without hitting the database at all. In WordPress, the Transients API provides a built-in mechanism for this: results are stored in wp_options with an expiration time, or — when an object cache is installed — in memory. Query-level caching is distinct from page caching (which bypasses PHP entirely) and from object caching (which stores in-memory data structures). It targets the application layer: specific queries that are slow, complex, or called repeatedly within a single request.

Impact grade, A+ to D: how much of the Lighthouse score this technique can move. Unaffected: TBT, CLS.

The Problem

WordPress executes dozens of database queries per page request by default: menu lookups, widget data, custom queries from themes and plugins, and meta retrieval. A single WooCommerce product listing page can fire 100+ queries. Complex WP_Query calls with meta or taxonomy JOINs, or queries on tables with millions of rows, can individually take 200–800 ms. Even if no single query is catastrophic, the cumulative DB time across all queries directly adds to TTFB and delays first paint.

Performance Impact

LCP

Indirect

faster TTFB means HTML arrives earlier, so all downstream resources start sooner

FCP

50–500ms

slower queries replaced with memory or disk lookups that take microseconds

SI

Indirect

faster page generation means content appears sooner in the viewport

TTFB

100–800ms

most of WordPress TTFB is PHP + DB time; caching slow queries directly cuts this

Risk: Low

transients expire automatically and regenerate on miss — stale data risk is bounded by TTL

How it works

  1. 01

    Identify slow or repeated queries

    Use Query Monitor (plugin) to profile queries per page. Look for duplicated queries (same SQL called multiple times per request) and slow queries (>50 ms). These are the highest-value caching targets.

  2. 02

    Cache results with the WordPress Transients API

    get_transient('key') returns the cached value if it exists. On cache miss, execute the query, then store the result with set_transient('key', $result, HOUR_IN_SECONDS). The transient is stored in wp_options by default, or in the object cache if one is installed.

  3. 03

    Use WP_Query's built-in caching fields

    WP_Query caches individual post objects and term data automatically via the object cache. Use 'update_post_meta_cache' => false and 'update_post_term_cache' => false on queries where you do not need post meta or terms, to skip those cache-population queries.

  4. 04

    Invalidate strategically

    Delete or update relevant transients on save_post, edited_term, and other relevant hooks so stale data is not served after content changes. Group related caches under a shared key prefix to make bulk invalidation simple.

Code Example

Before
<?php
// Called on every page load — executes a slow JOIN query each time
$featured_products = new WP_Query([
    'post_type'      => 'product',
    'meta_key'       => '_featured',
    'meta_value'     => 'yes',
    'posts_per_page' => 12,
]);
After
<?php
// Result cached for 1 hour — DB hit only on first load or after expiry
$featured_products = get_transient('featured_products');
if ( false === $featured_products ) {
    $featured_products = new WP_Query([
        'post_type'      => 'product',
        'meta_key'       => '_featured',
        'meta_value'     => 'yes',
        'posts_per_page' => 12,
    ]);
    set_transient('featured_products', $featured_products, HOUR_IN_SECONDS);
}

Invalidate the transient when products change: add_action('save_post_product', function(){ delete_transient('featured_products'); });

Limitations

  • Transients stored in wp_options are loaded from the database — they are faster than re-executing the original query, but not as fast as Redis or Memcached. Install an object cache plugin for maximum benefit.
  • Caching WP_Query objects stores post data including post content — ensure sensitive data is not cached in transients visible to other code paths.
  • Overly long TTLs cause stale data; overly short TTLs cause cache churn. Match the TTL to how frequently the underlying data actually changes.

Common Mistakes

Caching inside a loop

Calling get_transient() inside a loop for per-post data is still one DB call per iteration. Cache the entire result set outside the loop, not individual items within it.

Not invalidating caches when content changes

A transient with a 24-hour TTL will serve stale data for up to 24 hours after a post is published. Hook into save_post and related actions to proactively delete affected transients.

Test this techniqueCache AnalysisRun the tool

Targeted fixes, not guesswork.

Start an Engagement
Support our family now