Commit 362f1b3fcad for woocommerce
commit 362f1b3fcad0ee716a8f3ee99096cd1258b7c5c4
Author: Vladimir Reznichenko <kalessil@gmail.com>
Date: Fri Oct 2 08:52:43 2026 +0200
Performance: bulk-prime variable products transients on shop and category pages to reduce the number of SQL queries. (#69319)
Performs bulk priming of variable product transients on the shop and category pages.
Collapses N individual priming SQLs into a single one covering all variable products on the page.
diff --git a/plugins/woocommerce/changelog/performance-shop-page-variable-products-sqls-count-reduction b/plugins/woocommerce/changelog/performance-shop-page-variable-products-sqls-count-reduction
new file mode 100644
index 00000000000..b53c0af9ef3
--- /dev/null
+++ b/plugins/woocommerce/changelog/performance-shop-page-variable-products-sqls-count-reduction
@@ -0,0 +1,4 @@
+Significance: minor
+Type: performance
+
+Bulk-prime variable products transients on shop and category pages to reduce the number of SQL queries.
diff --git a/plugins/woocommerce/includes/class-wc-query.php b/plugins/woocommerce/includes/class-wc-query.php
index b35222a72c6..acea738d142 100644
--- a/plugins/woocommerce/includes/class-wc-query.php
+++ b/plugins/woocommerce/includes/class-wc-query.php
@@ -10,6 +10,7 @@ use Automattic\WooCommerce\Internal\ProductAttributesLookup\Filterer;
use Automattic\WooCommerce\Internal\ProductFilters\Params;
use Automattic\WooCommerce\Internal\Utilities\ProductUtil;
use Automattic\WooCommerce\Enums\ProductStockStatus;
+use Automattic\WooCommerce\Enums\ProductType;
use Automattic\WooCommerce\Enums\TaxDisplayMode;
defined( 'ABSPATH' ) || exit;
@@ -475,6 +476,7 @@ class WC_Query {
* queries during rendering.
*
* @since 10.8.0
+ * @internal
*
* @param array $posts Posts from WP Query.
* @param WP_Query $query Current query.
@@ -487,6 +489,48 @@ class WC_Query {
return $posts;
}
+ /**
+ * Prime transient option caches for variable products in bulk to avoid per-product queries during rendering.
+ *
+ * @since 11.3.0
+ * @internal
+ *
+ * @param array $posts Posts from WP Query.
+ * @param WP_Query $query Current query.
+ * @return array
+ */
+ public function prime_variable_products_transients( $posts, $query ) {
+ if ( 'product_query' === $query->get( 'wc_query' ) && ! wp_using_ext_object_cache() ) {
+ $options = array();
+ // Performance note: post caches has been already primed, hence the loop operates on in-memory data.
+ foreach ( $posts as $post ) {
+ $terms = $post instanceof \WP_Post ? get_object_term_cache( $post->ID, 'product_type' ) : null;
+ $terms = array_filter( is_array( $terms ) ? $terms : array(), static fn ( $term ) => $term instanceof \WP_Term ); // @phpstan-ignore instanceof.alwaysTrue (defensive checks against filters)
+ if ( ! empty( $terms ) && ProductType::VARIABLE === current( $terms )->slug ) {
+ $product_id = $post->ID;
+
+ // See also \WC_Product_Variable_Data_Store_CPT::read_product_data and sync with it when necessary.
+ $options[] = '_transient_wc_var_prices_' . $product_id;
+ $options[] = '_transient_timeout_wc_var_prices_' . $product_id;
+ $options[] = '_transient_wc_product_children_' . $product_id;
+ $options[] = '_transient_timeout_wc_product_children_' . $product_id;
+ $options[] = '_transient_wc_child_has_weight_' . $product_id;
+ $options[] = '_transient_timeout_wc_child_has_weight_' . $product_id;
+ $options[] = '_transient_wc_child_has_dimensions_' . $product_id;
+ $options[] = '_transient_timeout_wc_child_has_dimensions_' . $product_id;
+ $options[] = '_transient_wc_related_' . $product_id;
+ $options[] = '_transient_timeout_wc_related_' . $product_id;
+ }
+ }
+
+ if ( ! empty( $options ) ) {
+ wp_prime_option_caches( $options );
+ }
+ }
+
+ return $posts;
+ }
+
/**
* Pre_get_posts above may adjust the main query to add WooCommerce logic. When this query is done, we need to ensure
* all custom filters are removed.
@@ -601,6 +645,7 @@ class WC_Query {
add_filter( 'posts_clauses', array( $this, 'product_query_post_clauses' ), 10, 2 );
add_filter( 'the_posts', array( $this, 'handle_get_posts' ), 10, 2 );
add_filter( 'the_posts', array( $this, 'prime_thumbnail_caches' ), 10, 2 );
+ add_filter( 'the_posts', array( $this, 'prime_variable_products_transients' ), 10, 2 );
do_action( 'woocommerce_product_query', $q, $this );
}