diff --git a/src/wp-includes/class-wp-query.php b/src/wp-includes/class-wp-query.php index 21127dc755f1c..6863798def7b4 100644 --- a/src/wp-includes/class-wp-query.php +++ b/src/wp-includes/class-wp-query.php @@ -607,6 +607,7 @@ public function fill_query_vars( $query_vars ) { 'fields', 'menu_order', 'embed', + 'rand_factor', ); foreach ( $keys as $key ) { @@ -768,6 +769,8 @@ public function fill_query_vars( $query_vars ) { * @type int $posts_per_page The number of posts to query for. Use -1 to request all posts. * @type int $posts_per_archive_page The number of posts to query for by archive page. Overrides * 'posts_per_page' when is_archive(), or is_search() are true. + * @type float $rand_factor The factor used to determine the probability for optimized + * random ordering. Default 3.0. * @type string $s Search keyword(s). Prepending a term with a hyphen will * exclude posts matching that term. Eg, 'pillow -sofa' will * return posts containing 'pillow' but not 'sofa'. The @@ -2911,6 +2914,10 @@ public function get_posts() { $where = 'AND 0'; } } + // Orderby Rand Optimization. + if ( isset( $q['orderby'] ) && 'rand' === $q['orderby'] && empty( $q['nopaging'] ) ) { + $where = $this->_optimize_rand_orderby( $q, $where, $join ); + } $pieces = array( 'where', 'groupby', 'join', 'orderby', 'distinct', 'fields', 'limits' ); @@ -5115,4 +5122,48 @@ public function lazyload_comment_meta( $check, $comment_id ) { _deprecated_function( __METHOD__, '4.5.0' ); return $check; } + + /** + * Optimizes ORDER BY RAND() queries using probability sampling. + * Modifies the WHERE clause to include a probability condition based on RAND(). + * See: https://core.trac.wordpress.org/ticket/18836 + * + * @since 6.8.0 + * + * @global wpdb $wpdb WordPress database abstraction object. + * + * @param array $q Query vars. + * @param string $where The current WHERE clause. + * @param string $join The current JOIN clause. + * @return string The potentially modified WHERE clause. + */ + protected function _optimize_rand_orderby( $q, $where, $join ) { + global $wpdb; + + // Default factor to over-select rows, configurable via query var. + $rand_factor = isset( $q['rand_factor'] ) ? (float) $q['rand_factor'] : 3.0; + $size = absint( $q['posts_per_page'] ); + if ( $size <= 0 ) { + // If post_per_page is -1 set at least the default posts_per_page. + $size = (int) get_option( 'posts_per_page' ); + } + + // Use a simpler count query that respects existing WHERE and JOIN clauses. + $count_query = "SELECT COUNT(*) FROM {$wpdb->posts} {$join} WHERE 1=1 {$where}"; + + $row_count = (int) $wpdb->get_var( $count_query ); + + if ( $row_count > 0 && $size > 0 ) { + // Calculate probability, capped at 1.0 (100%). + $probability = min( 1.0, ( $size * $rand_factor ) / $row_count ); + + // Use seed if provided in query vars (e.g., 'orderby' => 'rand', 'seed' => 123). + $rand_call = isset( $q['seed'] ) ? $wpdb->prepare( 'RAND(%d)', $q['seed'] ) : 'RAND()'; + + // Add the probability condition to the main $where clause. + $where .= $wpdb->prepare( " AND {$rand_call} <= %f ", $probability ); + } + + return $where; + } }