-
Notifications
You must be signed in to change notification settings - Fork 3.2k
#47280 Remove usage of deprecated MySQL SQL_CALC_FOUND_ROWS from WP_Query
#3863
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: trunk
Are you sure you want to change the base?
#47280 Remove usage of deprecated MySQL SQL_CALC_FOUND_ROWS from WP_Query
#3863
Conversation
SQL_CALC_FOUND_ROWSSQL_CALC_FOUND_ROWS from WP_Query
|
|
||
| $this->request = $old_request; | ||
| $this->count_request = " | ||
| SELECT COUNT(DISTINCT {$count_field}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't DISTINCT be $distinct. Adding when its not needed results in more work for the DB.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As you're counting "found rows" here, you really don't want it to be distinct (as distinct rows <= actual rows). So technically DISTINCT should be omitted.
{$count_field} could be left as a * literal like the MySQL documents suggest, so if a $where clause exist, to choose from whatever available index suites the query best.
Look at the explain plan for when there's a WHERE clause that can use an index. Might not make a difference. Also see explain plain when a JOIN is added.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding DISTINCT, I'll check this again to be 100% sure but I think this needs to remain in place because some queries can result in duplicate rows being returned. These tests cover that scenario:
test_found_posts_are_correct_for_OR_meta_queries()test_found_posts_are_correct_for_group_by_queries()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I checked this again and SELECT COUNT(DISTINCT {$count_field}) is indeed needed for the scenarios covered by the two tests above. If there was a way to only add the DISTINCT clause when we knew we needed it that would be great, but I'm not too confident about that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@johnbillion Not testing the code. But it looks like DISTINCT is needed only when groupby field is used.
peterwilsoncc
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added a few notes inline but nothing major.
Are you able to set up a PR against trunk with the tests to ensure the suite passes on trunk with the exception of the SQL_CALC_FOUND_ROWS related assertions? This can wait if you've still got some wrapping up of this PR to do.
| * @param string $fields Value of the `fields` argument for `WP_Query`. | ||
| */ | ||
| public function test_found_posts_are_correct_for_basic_query( $fields ) { | ||
| self::factory()->post->create_many( 5 ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This appears multiple times, is it possible to create it as a shared fixture. It might need to be a CPT to avoid messing up the assertions elsewhere in this file.
Co-authored-by: Peter Wilson <519727+peterwilsoncc@users.noreply.github.com>
Co-authored-by: Peter Wilson <519727+peterwilsoncc@users.noreply.github.com>
| * Filters the query to run for retrieving the found posts. | ||
| * | ||
| * @since 2.1.0 | ||
| * @since x.x.x This query was changed from `SELECT FOUND_ROWS()` to a more |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a thought, I wonder if we could make it so we can call set_found_posts by itself with the rest of the call. We have examples in core where we get one post to get the count see.
wordpress-develop/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php
Line 402 in 722f737
| $count_query->query( $query_args ); |
| * @param string $request The complete SQL query. | ||
| * @param WP_Query $query The WP_Query instance (passed by reference). | ||
| */ | ||
| $this->request = apply_filters_ref_array( 'posts_request', array( $this->request, &$this ) ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@johnbillion We also need to check the posts_request_ids filter as well.
| if ( is_string( $this->request ) && str_contains( $this->request, 'SQL_CALC_FOUND_ROWS' ) ) { | ||
| _deprecated_argument( | ||
| 'The posts_request filter', | ||
| 'x.x.x', | ||
| sprintf( | ||
| /* translators: 1: SQL query modifier 2: SQL query */ | ||
| __( 'The %1$s query modifier should no longer be added to queries because results are no longer counted with %2$s by default.' ), | ||
| '<code>SQL_CALC_FOUND_ROWS</code>', | ||
| '<code>SELECT FOUND_ROWS()</code>' | ||
| ) | ||
| ); | ||
|
|
||
| $this->count_request = 'SELECT FOUND_ROWS()'; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As we are checking both posts_request_ids and posts_request filters, let's make this generic, reusable and make it into it's own method. This will also make it more testable.
| $count_field = "{$wpdb->posts}.ID"; | ||
|
|
||
| if ( ! empty( $groupby ) ) { | ||
| $count_field = $groupby; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would love some unit tests for some weird and wonder groupby values.
|
Maybe related - https://bugs.mysql.com/bug.php?id=21849 |
|
Any updates here please? SQL_CALC_FOUND_ROWS is twice as slow as count+select in our use cases and would mean massive performance improvements. https://core.trac.wordpress.org/ticket/47280 is almost at the finish line. |
|
what's blocking here? i'd love to help fix this. |
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Unlinked AccountsThe following contributors have not linked their GitHub and WordPress.org accounts: @grooverdan, @archon810, @mibmo. Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases. Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |


Trac ticket: https://core.trac.wordpress.org/ticket/47280
This carries on the work started on #330 and #2119. Lazy loading the
found_postsandmax_num_pageswas an interesting exercise but upon discussion with colleagues it's become clear that it'll result in potentially inaccurate counts if either of those properties are accessed after changes have been made to a post that exists in the result set:This PR implements the changes from #2119 minus the lazy loading of those two properties. It's fully compatible with the
WP_Queryquery caching that was introduced in WordPress 6.1.High level changes:
SQL_CALC_FOUND_ROWSandSELECT FOUND_ROWS()with an unboundedCOUNTof the query, which is the method recommended by MySQL and is more efficientSELECT FOUND_ROWS()only as a fallback if the query is filtered and aSQL_CALC_FOUND_ROWSmodifier inserted (low likelihood but needs to be handled)Details and todos:
no_found_rowsfound_posts_queryfilter: https://wpdirectory.net/search/01G2JTDXB9WQ0H1Y4P0VQSFBE3SELECT FOUND_ROWS()posts_requestfilter (the only filter that runs between the query being constructed and executed) https://wpdirectory.net/search/01G695ZX9RZ03PCDN3C0HFV6A1 . This may be a blocker.SQL_CALC_FOUND_ROWS, need to retain the use of an immediate call toSELECT FOUND_ROWS()._deprecated_argument()no_found_rowsis true then this is not a concern because the count query doesn't runDISTINCTclause can be removed from the count queryDISTINCTis not used by core but could be used by plugins via the various clause filtersGROUP BYcan be triggered by core, eg via a meta query with two or more clauses, anORrelation, and resulting posts that match more than one meta clause -- tests have been added to cover thisDISTINCTclause in theCOUNT()query should remainPlugin authors:
If you're the maintainer of a plugin which makes changes to the way WordPress queries for posts, counts results, or uses the
found_posts_queryorposts_requestfilter, please test your plugin with this change in place.Other query classes
Once
WP_Queryis complete, the process can be repeated for all query classes that can perform count queries:WP_Network_QueryWP_Site_QueryWP_Comment_QueryWP_User_Query