Make WordPress Core


Ignore:
Timestamp:
05/08/2024 10:49:46 PM (12 months ago)
Author:
spacedmonkey
Message:

Query: Improve cache key generation.

Query caching in WP_Query was added in [53941]. When this functionality was added to the WP_Query class, a number of edge cases were missed that would result in redundant duplicate queries. It was possible to pass parameters to WP_Query that would be different but would result in the same database query being generated. As the cache key is generated from a mixture of query arguments and the SQL query, this resulted in different cache keys for the same database query, resulting in unnecessary duplicate queries. In this change, the logic in the generate_cache_key method has been improved to ensure reuse of existing caches. The following edge cases have been considered:

  • Passing post_type as an empty string.
  • Passing post_type as the string any.
  • Passing post_type as array vs. string.
  • Passing post_type as an array in a different order.
  • Not passing orderby.
  • Passing post_status as an array vs. string.
  • Passing post_status as an array in a different order.

This change also fixes an issue where the old SQL query would not match, as the queries had different whitespaces.

Props spacedmonkey, joemcgill, pbearne, peterwilsoncc, rajinsharwar, mukesh27, thekt12, huzaifaalmesbah, rodionov201.
Fixes #59442.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/class-wp-query.php

    r57761 r58122  
    22652265                } elseif ( count( $post_type ) === 1 ) {
    22662266                    $post_type = $post_type[0];
     2267                } else {
     2268                    // Sort post types to ensure same cache key generation.
     2269                    sort( $post_type );
    22672270                }
    22682271
     
    25432546            }
    25442547        } elseif ( ! empty( $post_type ) && is_array( $post_type ) ) {
     2548            // Sort post types to ensure same cache key generation.
     2549            sort( $post_type );
    25452550            $post_type_where = " AND {$wpdb->posts}.post_type IN ('" . implode( "', '", esc_sql( $post_type ) ) . "')";
    25462551        } elseif ( ! empty( $post_type ) ) {
     
    26482653
    26492654            if ( ! empty( $queried_post_types ) ) {
    2650 
     2655                sort( $queried_post_types );
    26512656                $status_type_clauses = array();
    26522657
     
    31053110        }
    31063111
    3107         // Beginning of the string is on a new line to prevent leading whitespace. See https://core.trac.wordpress.org/ticket/56841.
     3112        /*
     3113         * Beginning of the string is on a new line to prevent leading whitespace.
     3114         *
     3115         * The additional indentation of subsequent lines is to ensure the SQL
     3116         * queries are identical to those generated when splitting queries. This
     3117         * improves caching of the query by ensuring the same cache key is
     3118         * generated for the same database queries functionally.
     3119         *
     3120         * See https://core.trac.wordpress.org/ticket/56841.
     3121         * See https://github.com/WordPress/wordpress-develop/pull/6393#issuecomment-2088217429
     3122         */
    31083123        $old_request =
    31093124            "SELECT $found_rows $distinct $fields
    3110             FROM {$wpdb->posts} $join
    3111             WHERE 1=1 $where
    3112             $groupby
    3113             $orderby
    3114             $limits";
     3125                    FROM {$wpdb->posts} $join
     3126                    WHERE 1=1 $where
     3127                    $groupby
     3128                    $orderby
     3129                    $limits";
    31153130
    31163131        $this->request = $old_request;
     
    48574872        );
    48584873
     4874        if ( empty( $args['post_type'] ) ) {
     4875            if ( $this->is_attachment ) {
     4876                $args['post_type'] = 'attachment';
     4877            } elseif ( $this->is_page ) {
     4878                $args['post_type'] = 'page';
     4879            } else {
     4880                $args['post_type'] = 'post';
     4881            }
     4882        } elseif ( 'any' === $args['post_type'] ) {
     4883            $args['post_type'] = array_values( get_post_types( array( 'exclude_from_search' => false ) ) );
     4884        }
     4885        $args['post_type'] = (array) $args['post_type'];
     4886        // Sort post types to ensure same cache key generation.
     4887        sort( $args['post_type'] );
     4888
     4889        if ( isset( $args['post_status'] ) ) {
     4890            $args['post_status'] = (array) $args['post_status'];
     4891            // Sort post status to ensure same cache key generation.
     4892            sort( $args['post_status'] );
     4893        }
     4894
     4895        // Add a default orderby value of date to ensure same cache key generation.
     4896        if ( ! isset( $q['orderby'] ) ) {
     4897            $args['orderby'] = 'date';
     4898        }
     4899
    48594900        $placeholder = $wpdb->placeholder_escape();
    48604901        array_walk_recursive(
     
    48754916        );
    48764917
     4918        ksort( $args );
     4919
    48774920        // Replace wpdb placeholder in the SQL statement used by the cache key.
    48784921        $sql = $wpdb->remove_placeholder_escape( $sql );
Note: See TracChangeset for help on using the changeset viewer.