Querying posts can be tricky. There are many ways to fetch content, and it’s not always clear what will give you the most benefits. The answers I’ve seen are also not always straight-forward and the “why” is not always explained.
Here’s my attempt to give basic advice on how to write performant WordPress Queries.
no_found_rows
When doing a query, WordPress will want to calculate how many rows of results are possible for your query so that it can properly set pagination. That means even if you only ask for 1 post, WP_Query
will still check all possible results to calculate the number of rows found.
Let’s imagine a few scenarios where that could get complicated:
“Get me the latest 5 weather reports”
What if you have a weather report every hour? Even if you limit the query to 5 reports, you might be having to thousands of rows of data just to get 5 of them.
This can get hairy quickly if there are a lot of rows to calculate. If you have a posts table with 100 posts – no problem! However, say you had 100,000 posts and you only wanted 5 – WordPress would add the SQL_CALC_FOUND_ROWS
function to the SQL query to figure out pagination, creating a larger query load than needed.
Luckily, WP_Query
lets you set the 'no_found_rows' => true
argument to skip that calculation and save time.
If you’re using get_posts
, it sets no_found_rows
to true by default.
When you don’t need pagination, set
'no_found_rows' => true
.
ignore_sticky_posts
Do you use sticky
posts? I sometimes forget they even exist. However, WordPress queries will check them by default unless you tell them not to.
What WP_Query
will do is fetch all the posts requested, and if it finds sticky
posts, it’ll loop through every one of them and reorder the array so they are at the top. If there are sticky posts outside of what was fetched, it’ll run another sql query to just grab those posts.
What does that look like for performance? It depends! Do you have a lot of sticky
posts?
Say you want to get the latest 10 posts and you have 5 sticky posts that are older than those 10 posts – that means you are now doing 2 queries, getting a total of 15 posts, and then looping through each of them to reorder them. Not a big performance hit but still more work.
What if you were fetching 100 posts, and you had 20 sticky posts outside of that range?
You can see where this could get problematic.
Set
'ignore_sticky_posts' => true
whenever possible to prevent extra queries or additional time spent reordering results.
include_children
If you’re doing a taxonomy query, include_children
will be set to true by default. That means that if you’re querying for posts in a category of Parent
and that category has Child 1
, Child 2
, and Child 3
– your query will grab posts for all of those categories and not just the one you defined.
Taxonomy queries are already not the most efficient because they’re querying multiple tables and doing a JOIN
operation on the results so adding more complexity with children taxonomies could mean make them perform worse.
Set
'include_children' => false
when doing a taxonomy query if you can.
Be specific
This is probably the most important part of efficient querying in WordPress (or any platform). You should be specific without being too complicated. In other words, get the smallest amount of data in the least complicated way.
'fields' => 'ids'
Say you want to display the featured images of 10 posts. Instead of doing a query for the entire posts and plucking the IDs out, just do a query for the IDs themselves with 'fields' => 'ids'
!
posts_per_page
Don’t abuse posts_per_page
– try to stick with the minimum amount possible.
post_type
and post_status
If you know the post_type
and post_status
you need, make sure to declare them! This could save a lot of time in the query.
date_query
Using a date_query
can be really beneficial as well. If you want to grab the latest posts, instead of grabbing a large amount of posts and grouping by date – use a date_query
to only get posts after a certain time. Here’s an example to only query posts from the past 6 months:
add_action( 'pre_get_posts', function($query) {
if ( $query->is_main_query() && $query->is_archive() ) {
$query->set( 'date_query', array(
array(
'after' => date( 'Y-m-d', strtotime('-6 months')),
),
) );
}
});
Advanced Post Cache
Advanced Post Cache (APC) is a plugin used on many WordPress platforms. It’s enabled by default on WordPress VIP. If you haven’t heard of it before, give it a quick read, it’s not very large!
What APC does is filter queries to cache them. For example, it will filter through posts_request
to query the items returned.
If you’re using APC, make sure you set 'suppress_filters' => false
. That will ensure the queries are filtered by APC and cached. WP_Query
will set suppress_filters
to false by default, get_posts()
will set it to true
by default.
There isn’t really a “one size fits all” approach here, it’s really more about learning to think about how you query data and how WordPress’ wrappers work so you can get the most out of them.
Leave a Reply