[wp-trac] [WordPress Trac] #50161: Slow query in WP_Posts_List_Table, no way to avoid with filters/hooks
WordPress Trac
noreply at wordpress.org
Tue Jul 1 19:02:07 UTC 2025
#50161: Slow query in WP_Posts_List_Table, no way to avoid with filters/hooks
-------------------------------+------------------------------------------
Reporter: LucasMS | Owner: (none)
Type: enhancement | Status: reopened
Priority: normal | Milestone:
Component: Posts, Post Types | Version: 5.4.1
Severity: normal | Resolution:
Keywords: | Focuses: administration, performance
-------------------------------+------------------------------------------
Comment (by josephscott):
To fill out the details on the potential impact of a new index to speed
things the `All Posts` page up I setup a fresh WP 6.8.1 install with PHP
8.4.7, MySQL 8.4.2 on my Apple M3 laptop ( no memcached or redis ).
I'm specifically looking at how long it takes for the initial page request
to finish for `/wp-admin/edit.php`. I generated 340,000 posts for this
test. They are spread across two users, and the admin user view of the
`All Posts` page shows:
- All: 340,000
- Mine: 339,999
- Published: 339,800
- Drafts: 100
- Private: 100
Again, looking only at how long the initial page request for `/wp-
admin/edit.php` was taking, I was seeing times in the 0.820 seconds range.
This wasn't TTFB, it was the total time to get the single HTML page
response back. After enabling SPX to get an idea of which parts were
taking the longest, the vast majority of the time was spent in
`wpdb::_do_query`, which wasn't a huge surprise.
But looking at just the `_do_query` parts from the SPX report showed that
one query in particular was the slowest part. It was the query from
class-wp-posts-list-table.php -
https://github.com/WordPress/WordPress/blob/2f41f3fdaded86e2f4baca87001138542fb7fc55
/wp-admin/includes/class-wp-posts-list-table.php#L92-L102
{{{
$this->user_posts_count = (int) $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT( 1 )
FROM $wpdb->posts
WHERE post_type = %s
AND post_status NOT IN ( '" . implode(
"','", $exclude_states ) . "' )
AND post_author = %d",
$post_type,
get_current_user_id()
)
);
}}}
In the SPX captured request it indicated that query was taking 0.360
seconds. That was more than 40% of the total time to request the page.
To isolate just the query, I started running it directly against MySQL.
In my case that was:
{{{
SELECT COUNT( 1 )
FROM wp_posts
WHERE post_type = 'post'
AND post_status NOT IN ( 'trash','auto-draft','inherit
','request-pending','request-confirmed','request-failed','request-
completed' )
AND post_author = 1
}}}
Doing that 10 times gave me a p75 response time of 0.346 seconds ( min:
0.341 seconds, max: 0.356 seconds ). That confirms that this query alone
was taking up a big chunk of time to finish the request.
Adding `EXPLAIN` to that query reported:
- id: 1
- select_type: SIMPLE
- table: wp_posts
- partitions: NULL
- type: ref
- possible_keys: type_status_date,post_author
- key: post_author
- key_len: 8
- ref: const
- rows: 145,414
- filtered: 50.04
- extra: Using where
Trying a few different things, I eventually landed on this new index:
{{{
CREATE INDEX type_status_author ON wp_posts (post_type, post_status,
post_author);
}}}
Running the same `COUNT( 1 )` query another 10 times gave a new p75
response time of 0.123 seconds ( min: 0.119 seconds, max: 0.125 seconds ).
Doing `EXPLAIN` on the query with the new index in place reported:
- id: 1
- select_type: SIMPLE
- table: wp_posts
- partitions: NULL
- type: range
- possible_keys: type_status_date,post_author,type_status_author
- key: type_status_author
- key_len: 164
- ref: NULL
- rows: 145,520
- filtered: 50.00
- extra: Using where; Using index
To confirm the impact that this would have on the full `All Posts` ( `/wp-
admin/edit.php` ) HTTP request I ran ten curl requests for it before and
after. This is not TTFB, it was the total time the curl request took.
- p75 BEFORE: 0.826 seconds ( min: 0.812 seconds, max: 0.829 seconds )
- p75 AFTER: 0.558 seconds ( min: 0.550 seconds, max: 0.563 seconds )
That confirmed that adding the index, speeding up that single query,
reduced the time for the `All Posts` HTTP page request significantly.
Speeding up a request by 32% doesn't come along every day.
Adding an index does come with some trade offs. Inserting into the
`wp_posts` table will require an update to the new index. In my test
setup I found that inserts took about 0.0001 seconds longer at p75 with
the new index in place. That seems small enough that I don't think it is
a concern.
The other part of having a new index is space. I ran the following query
to get a list of the index sizes ( in MB ) for the `wp_posts` table:
{{{
SELECT
database_name,
TABLE_NAME,
index_name,
ROUND(
stat_value * @@innodb_page_size / 1024 / 1024,
2
) size_in_mb
FROM
mysql.innodb_index_stats
WHERE
stat_name = 'size' AND index_name != 'PRIMARY' AND database_name =
'wordpress' AND TABLE_NAME = 'wp_posts'
ORDER BY
size_in_mb
DESC;
}}}
- post_name: 30.6 MB
- type_status_date: 18.5 MB
- type_status_author: 13.5 MB
- post_author: 7.5 MB
- post_parent: 7.5 MB
Adding 13.5 MB to the set of indexes is a sizable increase. But in the
bigger picture, the `wp_posts` table is also 500 MB. Making a 13.5 MB new
index a 2.7% increase.
All in all, I think the trade off of having a new index is worth the
improvement.
--
Ticket URL: <https://core.trac.wordpress.org/ticket/50161#comment:7>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform
More information about the wp-trac
mailing list