Most of a duplicate-query list converges on one shape: a loop over posts that reads a meta value for each one. Nothing about it looks wrong.
foreach ( $ids as $id ) {
$price = get_post_meta( $id, 'price', true );
}
How many queries that produces depends entirely on whether the cache was primed. Primed, it is zero. Cold, it is one query per post. The same code behaving differently on different screens is what makes this hard to spot.
WP_Query primes its own results
After fetching results, WP_Query loads the meta and terms for those posts in one go and puts them in the object cache. That is why get_post_meta() inside an ordinary loop issues no queries at all — the values are already there. Core was built with this problem in mind.
The trouble is lists that leave that path. Four cases cover most of it.
The third is the real trap. 'update_post_meta_cache' => false is a genuine optimisation for a list that touches no meta at all, but it circulates as a general performance tip and gets pasted into loops that do read meta. You save one query and buy N.
The priming functions core already ships
You do not have to touch the cache API yourself.
$ids = $wpdb->get_col(
$wpdb->prepare( "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = %s", 'featured' )
);
// posts, terms and meta — one query each
_prime_post_caches( $ids, true, true );
foreach ( $ids as $id ) {
$price = get_post_meta( $id, 'price', true ); // cache hit
}
If you only need meta, update_postmeta_cache( $ids ) does that alone; for terms it is update_object_term_cache( $ids, 'product' ), and for a list of users, cache_users( $user_ids ). In every case, N lookups collapse into one.
One detail people get wrong: get_post_meta( $id, 'price', true ) reads all of that post’s meta, not just the one key, and caches the lot. Reading five keys from the same post is still one query. The N in N+1 is the number of posts, not the number of keys.
Confirming that you fixed it
Once the priming call is in, go back to the profile from the previous part and re-read it on the same URL under the same conditions. Check two things: that the repeat count for that SQL has dropped to one, and that total response time actually moved.
The second check matters. Sometimes the repetition vanishes and the response time does not move, which means those queries were always cheap and the bottleneck is elsewhere — write that down too. “No measurable change” is information for whoever picks this up next.
More on query design for listing screens is in the performance archive, and a full review of a site’s query structure is part of the diagnostic in our optimization program.
Next part
Priming removes repetition within one request. Keeping it between requests needs a persistent object cache — next we separate what Redis genuinely removes from what it does not.