WordPress executes dozens of database queries on every page load — fetching options, post data, term relationships, and user meta — and on busy sites these queries can become a significant bottleneck. The WordPress object cache is a built-in in-memory store that allows you to cache the results of expensive operations for the duration of a single page request, eliminating redundant database hits. With a persistent cache backend like Redis or Memcached, the cache survives across requests and can serve cached data to thousands of visitors without a single database query. Even without a persistent backend, the built-in non-persistent cache (which WordPress uses by default) can dramatically reduce the number of queries on a single request by avoiding duplicate lookups. The core API functions are wp_cache_get(), wp_cache_set(), and wp_cache_delete(). For data that should persist across requests but does not require a full object cache server, the Transients API stores values in the database with an expiry time. The object cache and transients complement each other: use the object cache for within-request deduplication and transients for cross-request persistence. Combining both with regular database optimisation and query analysis with EXPLAIN gives you a comprehensive performance strategy. The code below shows practical patterns for caching custom database queries.
Problem: A custom database query runs multiple times per request (or on every page load) and you need to cache its result to reduce database load.
Solution: Add the following code to your functions.php file:
/**
* Cache a custom DB query for the duration of the request (non-persistent).
* With a persistent cache backend (Redis/Memcached), this survives across requests.
*/
function helloadmin_get_featured_authors() {
$cache_key = 'helloadmin_featured_authors';
$cache_group = 'helloadmin';
$authors = wp_cache_get( $cache_key, $cache_group );
if ( false !== $authors ) {
return $authors; // cache hit
}
global $wpdb;
$authors = $wpdb->get_results( $wpdb->prepare(
"SELECT u.ID, u.display_name, COUNT(p.ID) AS post_count
FROM {$wpdb->users} u
JOIN {$wpdb->posts} p ON p.post_author = u.ID
WHERE p.post_status = %s AND p.post_type = %s
GROUP BY u.ID
ORDER BY post_count DESC
LIMIT %d",
'publish', 'post', 5
) );
wp_cache_set( $cache_key, $authors, $cache_group, HOUR_IN_SECONDS );
return $authors;
}
/**
* Invalidate the cache when a post is published or updated.
*/
add_action( 'save_post', 'helloadmin_flush_author_cache' );
function helloadmin_flush_author_cache() {
wp_cache_delete( 'helloadmin_featured_authors', 'helloadmin' );
}
/**
* Transient alternative — persists in the DB even without a cache server.
*/
function helloadmin_get_stats() {
$stats = get_transient( 'helloadmin_site_stats' );
if ( false !== $stats ) {
return $stats;
}
global $wpdb;
$stats = [
'posts' => (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_status='publish' AND post_type='post'" ),
'users' => (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->users}" ),
];
set_transient( 'helloadmin_site_stats', $stats, 12 * HOUR_IN_SECONDS );
return $stats;
}
NOTE: The default WordPress object cache is non-persistent, meaning it is destroyed at the end of each PHP request. To make wp_cache_set() / wp_cache_get() persist across requests, you need to install a persistent cache backend: place a object-cache.php drop-in file in wp-content/ (provided by Redis Object Cache, W3 Total Cache, or similar plugins). Without it, the cache group and expiry arguments to wp_cache_set() are silently ignored.