Database Query Cache
Cache the results of expensive or repeated database queries to avoid re-executing them on every request.
Metrics
This technique can move up to 67% of LCP's share of the Lighthouse score.
This technique does not move TBT.
This technique does not move CLS.
This technique can move up to 67% of FCP's share of the Lighthouse score.
This technique can move up to 33% of SI's share of the Lighthouse score.
This technique can move up to 67% of TTFB's share of the Lighthouse score.
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
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.
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 withset_transient('key', $result, HOUR_IN_SECONDS). The transient is stored inwp_optionsby default, or in the object cache if one is installed.03
Use WP_Query's built-in caching fields
WP_Querycaches individual post objects and term data automatically via the object cache. Use'update_post_meta_cache' => falseand'update_post_term_cache' => falseon queries where you do not need post meta or terms, to skip those cache-population queries.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
<?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,
]);<?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.