Commit 239412da74 for woocommerce

commit 239412da74bcf10c3ffa07c5081b28c1ddaa423f
Author: Vladimir Reznichenko <kalessil@gmail.com>
Date:   Tue Feb 3 10:51:25 2026 +0100

    [Performance] Products: reduce the number of SQLs when rendering related/upsell products. (#63006)

    When rendering a product collection, priming caches at the collection level instead of the product level reduces the number of SQL queries. As a result, the number of SQL queries executed during cache priming remains constant, regardless of the collection size.

diff --git a/plugins/woocommerce/changelog/performance-62988-cache-priming-in-blocks b/plugins/woocommerce/changelog/performance-62988-cache-priming-in-blocks
new file mode 100644
index 0000000000..0aed678f56
--- /dev/null
+++ b/plugins/woocommerce/changelog/performance-62988-cache-priming-in-blocks
@@ -0,0 +1,4 @@
+Significance: patch
+Type: performance
+
+Products: reduced the number of SQL queries required to display upsell and related products on the product page.
diff --git a/plugins/woocommerce/includes/wc-template-functions.php b/plugins/woocommerce/includes/wc-template-functions.php
index 70419b23c6..4b296c3ec8 100644
--- a/plugins/woocommerce/includes/wc-template-functions.php
+++ b/plugins/woocommerce/includes/wc-template-functions.php
@@ -2265,11 +2265,21 @@ if ( ! function_exists( 'woocommerce_related_products' ) ) {

 		$args = wp_parse_args( $args, $defaults );

-		// Get visible related products then sort them at random.
-		$args['related_products'] = array_filter( array_map( 'wc_get_product', wc_get_related_products( $product->get_id(), $args['posts_per_page'], $product->get_upsell_ids() ) ), 'wc_products_array_filter_visible' );
+		$related_products    = array();
+		$related_product_ids = wc_get_related_products( $product->get_id(), $args['posts_per_page'], $product->get_upsell_ids() );
+		if ( ! empty( $related_product_ids ) ) {
+			// Optimization: reduce the number of SQLs needed to populate product objects.
+			_prime_post_caches( $related_product_ids );

-		// Handle orderby.
-		$args['related_products'] = wc_products_array_orderby( $args['related_products'], $args['orderby'], $args['order'] );
+			// Get visible related products then sort them at random, then handle orderby.
+			$related_products = array_filter( array_map( 'wc_get_product', $related_product_ids ), 'wc_products_array_filter_visible' );
+			$related_products = wc_products_array_orderby( $related_products, $args['orderby'], $args['order'] );
+			/** @var WC_Product[] $related_products */ // phpcs:ignore Generic.Commenting.DocComment.MissingShort
+
+			// Optimization: reduce the number of SQLs needed to fetch images when rendering.
+			_prime_post_caches( array_filter( array_map( fn( $product ) => (int) $product->get_image_id(), $related_products ) ) );
+		}
+		$args['related_products'] = $related_products;

 		// Set global loop values.
 		wc_set_loop_prop( 'name', 'related' );
@@ -2319,9 +2329,20 @@ if ( ! function_exists( 'woocommerce_upsell_display' ) ) {
 		 */
 		$limit = intval( apply_filters( 'woocommerce_upsells_total', $args['posts_per_page'] ?? $limit ) );

-		// Get visible upsells then sort them at random, then limit result set.
-		$upsells = wc_products_array_orderby( array_filter( array_map( 'wc_get_product', $product->get_upsell_ids() ), 'wc_products_array_filter_visible' ), $orderby, $order );
-		$upsells = $limit > 0 ? array_slice( $upsells, 0, $limit ) : $upsells;
+		$upsells    = array();
+		$upsell_ids = $product->get_upsell_ids();
+		if ( ! empty( $upsell_ids ) ) {
+			// Optimization: reduce the number of SQLs needed to populate product objects.
+			_prime_post_caches( $upsell_ids );
+
+			// Get visible upsells then sort them at random, then limit result set.
+			$upsells = wc_products_array_orderby( array_filter( array_map( 'wc_get_product', $upsell_ids ), 'wc_products_array_filter_visible' ), $orderby, $order );
+			$upsells = $limit > 0 ? array_slice( $upsells, 0, $limit ) : $upsells;
+			/** @var WC_Product[] $upsells */ // phpcs:ignore Generic.Commenting.DocComment.MissingShort
+
+			// Optimization: reduce the number of SQLs needed to fetch images when rendering.
+			_prime_post_caches( array_filter( array_map( fn( $product ) => (int) $product->get_image_id(), $upsells ) ) );
+		}

 		wc_get_template(
 			'single-product/up-sells.php',
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/RelatedProducts.php b/plugins/woocommerce/src/Blocks/BlockTypes/RelatedProducts.php
index aa0c6ab848..a3f4a94969 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/RelatedProducts.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/RelatedProducts.php
@@ -145,7 +145,7 @@ class RelatedProducts extends AbstractBlock {
 	 */
 	private function is_related_products_block( $parsed_block, $rendered_block = null ) {
 		$is_product_collection_block = $rendered_block->context['query']['isProductCollectionBlock'] ?? false;
-		if ( ProductQuery::is_woocommerce_variation( $parsed_block ) && isset( $parsed_block['attrs']['namespace'] ) && 'woocommerce/related-products' === $parsed_block['attrs']['namespace'] && ! $is_product_collection_block ) {
+		if ( ! $is_product_collection_block && 'woocommerce/related-products' === ( $parsed_block['attrs']['namespace'] ?? null ) && ProductQuery::is_woocommerce_variation( $parsed_block ) ) {
 			return true;
 		}

@@ -157,9 +157,9 @@ class RelatedProducts extends AbstractBlock {
 	 * The logic is copied from the core function woocommerce_related_products. https://github.com/woocommerce/woocommerce/blob/ca49caabcba84ce9f60a03c6d3534ec14b350b80/plugins/woocommerce/includes/wc-template-functions.php/#L2039-L2074
 	 *
 	 * @param number $product_per_page Products per page.
-	 * @return array Products ids.
+	 * @return int[] Products ids.
 	 */
-	private function get_related_products_ids( $product_per_page = 5 ) {
+	private function get_related_products_ids( $product_per_page = 5 ): array {
 		global $post;

 		$product = wc_get_product( $post->ID );
@@ -168,16 +168,21 @@ class RelatedProducts extends AbstractBlock {
 			return array();
 		}

-		$related_products = array_filter( array_map( 'wc_get_product', wc_get_related_products( $product->get_id(), $product_per_page, $product->get_upsell_ids() ) ), 'wc_products_array_filter_visible' );
-		$related_products = wc_products_array_orderby( $related_products, 'rand', 'desc' );
+		$related_products_ids = wc_get_related_products( $product->get_id(), $product_per_page, $product->get_upsell_ids() );
+		if ( ! empty( $related_products_ids ) ) {
+			// Optimization: reduce the number of SQLs needed to populate product objects.
+			_prime_post_caches( $related_products_ids );

-		$related_product_ids = array_map(
-			function ( $product ) {
-				return $product->get_id();
-			},
-			$related_products
-		);
+			$related_products = array_filter( array_map( 'wc_get_product', $related_products_ids ), 'wc_products_array_filter_visible' );
+			$related_products = wc_products_array_orderby( $related_products, 'rand', 'desc' );
+			/** @var \WC_Product[] $related_products */ // phpcs:ignore Generic.Commenting.DocComment.MissingShort
+
+			// Optimization: reduce the number of SQLs needed to fetch images when rendering.
+			_prime_post_caches( array_filter( array_map( fn( $product ) => (int) $product->get_image_id(), $related_products ) ) );
+
+			$related_products_ids = array_map( fn( $product ) => $product->get_id(), $related_products );
+		}

-		return $related_product_ids;
+		return $related_products_ids;
 	}
 }