Commit 8360c2aef8 for woocommerce
commit 8360c2aef8d7ed590e4f154bfb491df7860e924d
Author: Vladimir Reznichenko <kalessil@gmail.com>
Date: Wed Feb 18 08:24:29 2026 +0100
[Performance] Orders: reduce the number of SQL queries required to persist a draft order during checkout (#63258)
Optimize purging customers' usermeta in `wc_downloadable_product_permissions`, which is called multiple times during order save. The optimization leverages reads as guard conditions, as reads are performed from the in-memory cache until deletions are necessary. Previously unguarded deletions were causing cache invalidation and re-read from DB.
diff --git a/plugins/woocommerce/changelog/performance-63118-reduce-sqls-number-iteration-2 b/plugins/woocommerce/changelog/performance-63118-reduce-sqls-number-iteration-2
new file mode 100644
index 0000000000..63cbfbf953
--- /dev/null
+++ b/plugins/woocommerce/changelog/performance-63118-reduce-sqls-number-iteration-2
@@ -0,0 +1,4 @@
+Significance: minor
+Type: performance
+
+Performance: reduced the number of SQL queries required to persist a draft order during checkout.
diff --git a/plugins/woocommerce/includes/class-wc-order.php b/plugins/woocommerce/includes/class-wc-order.php
index b433eac37c..8ff2252897 100644
--- a/plugins/woocommerce/includes/class-wc-order.php
+++ b/plugins/woocommerce/includes/class-wc-order.php
@@ -1875,8 +1875,9 @@ class WC_Order extends WC_Abstract_Order {
if ( false === $needs_processing ) {
$needs_processing = 0;
- if ( count( $this->get_items() ) > 0 ) {
- foreach ( $this->get_items() as $item ) {
+ $line_items = $this->get_items();
+ if ( count( $line_items ) > 0 ) {
+ foreach ( $line_items as $item ) {
if ( $item->is_type( 'line_item' ) ) {
$product = $item->get_product();
diff --git a/plugins/woocommerce/includes/class-wc-shipping.php b/plugins/woocommerce/includes/class-wc-shipping.php
index 91138393e1..62b28419ff 100644
--- a/plugins/woocommerce/includes/class-wc-shipping.php
+++ b/plugins/woocommerce/includes/class-wc-shipping.php
@@ -139,6 +139,9 @@ class WC_Shipping {
// For backwards compatibility with 2.5.x we load any ENABLED legacy shipping methods here.
$maybe_load_legacy_methods = array( 'flat_rate', 'free_shipping', 'international_delivery', 'local_delivery', 'local_pickup' );
+ // Prime the settings options: reduces the number of executed SQLs.
+ wp_prime_option_caches( array_map( fn( string $method ) => sprintf( 'woocommerce_%s_settings', $method ), $maybe_load_legacy_methods ) );
+
foreach ( $maybe_load_legacy_methods as $method ) {
$options = get_option( 'woocommerce_' . $method . '_settings' );
if ( $options && isset( $options['enabled'] ) && 'yes' === $options['enabled'] ) {
diff --git a/plugins/woocommerce/includes/wc-order-functions.php b/plugins/woocommerce/includes/wc-order-functions.php
index e7049ff6f9..635805097a 100644
--- a/plugins/woocommerce/includes/wc-order-functions.php
+++ b/plugins/woocommerce/includes/wc-order-functions.php
@@ -99,7 +99,7 @@ function wc_get_order( $the_order = false ) {
*
* @since 2.2
* @used-by WC_Order::set_status
- * @return array
+ * @return array<string,string>
*/
function wc_get_order_statuses() {
$order_statuses = array(
@@ -473,8 +473,9 @@ function wc_downloadable_product_permissions( $order_id, $force = false ) {
return;
}
- if ( count( $order->get_items() ) > 0 ) {
- foreach ( $order->get_items() as $item ) {
+ $line_items = $order->get_items();
+ if ( count( $line_items ) > 0 ) {
+ foreach ( $line_items as $item ) {
$product = $item->get_product();
if ( $product && $product->exists() && $product->is_downloadable() ) {
@@ -499,18 +500,32 @@ add_action( 'woocommerce_order_status_processing', 'wc_downloadable_product_perm
* @param int|WC_Order $order Order instance or ID.
*/
function wc_delete_shop_order_transients( $order = 0 ) {
- if ( is_numeric( $order ) ) {
+ if ( $order && is_numeric( $order ) ) {
$order = wc_get_order( $order );
}
// Clear customer's order related caches.
$order_id = 0;
- if ( is_a( $order, 'WC_Order' ) ) {
- $order_id = $order->get_id();
+ if ( $order && is_a( $order, 'WC_Order' ) ) {
+ $order_id = $order->get_id();
+
$customer_id = $order->get_customer_id();
- Users::delete_site_user_meta( $customer_id, 'wc_money_spent' );
- Users::delete_site_user_meta( $customer_id, 'wc_order_count' );
- Users::delete_site_user_meta( $customer_id, 'wc_last_order' );
+ if ( $customer_id ) {
+ // Optimization note: the function is fired multiple times during order persistence lifecycle, and by
+ // verifying that metas carry non-empty values we ensure no repetitive attempts dropping the metas.
+ $metas_to_purge = array_filter(
+ array(
+ is_numeric( Users::get_site_user_meta( $customer_id, 'wc_order_count' ) ) ? 'wc_order_count' : '',
+ is_numeric( Users::get_site_user_meta( $customer_id, 'wc_last_order' ) ) ? 'wc_last_order' : '',
+ is_numeric( Users::get_site_user_meta( $customer_id, 'wc_money_spent' ) ) ? 'wc_money_spent' : '',
+ )
+ );
+ if ( ! empty( $metas_to_purge ) ) {
+ foreach ( $metas_to_purge as $meta ) {
+ Users::delete_site_user_meta( $customer_id, $meta );
+ }
+ }
+ }
}
// Increments the transient version to invalidate cache.
@@ -668,9 +683,9 @@ function wc_create_refund( $args = array() ) {
// delete downloads that were refunded using order and product id, if present.
if ( ! empty( $refunded_order_and_products ) ) {
+ $download_data_store = WC_Data_Store::load( 'customer-download' );
foreach ( $refunded_order_and_products as $refunded_order_and_product ) {
- $download_data_store = WC_Data_Store::load( 'customer-download' );
- $downloads = $download_data_store->get_downloads( $refunded_order_and_product );
+ $downloads = $download_data_store->get_downloads( $refunded_order_and_product );
if ( ! empty( $downloads ) ) {
foreach ( $downloads as $download ) {
$download_data_store->delete_by_id( $download->get_id() );
@@ -950,12 +965,12 @@ function wc_update_total_sales_counts( $order_id ) {
$operation = $recorded_sales && $reflected_order ? 'decrease' : 'increase';
- if ( count( $order->get_items() ) > 0 ) {
- foreach ( $order->get_items() as $item ) {
+ $line_items = $order->get_items();
+ if ( count( $line_items ) > 0 ) {
+ $data_store = WC_Data_Store::load( 'product' );
+ foreach ( $line_items as $item ) {
$product_id = $item->get_product_id();
-
if ( $product_id ) {
- $data_store = WC_Data_Store::load( 'product' );
$data_store->update_product_sales( $product_id, absint( $item->get_quantity() ), $operation );
}
}
diff --git a/plugins/woocommerce/phpstan-baseline.neon b/plugins/woocommerce/phpstan-baseline.neon
index 41798a70d2..29a389d044 100644
--- a/plugins/woocommerce/phpstan-baseline.neon
+++ b/plugins/woocommerce/phpstan-baseline.neon
@@ -35550,7 +35550,7 @@ parameters:
-
message: '#^Parameter \#1 \$object_or_string of function is_a expects object, WC_Order\|WC_Order_Refund\|false given\.$#'
identifier: argument.type
- count: 2
+ count: 1
path: includes/wc-order-functions.php
-
diff --git a/plugins/woocommerce/tests/php/includes/wc-order-functions-test.php b/plugins/woocommerce/tests/php/includes/wc-order-functions-test.php
index 9b6f7b7113..657309492e 100644
--- a/plugins/woocommerce/tests/php/includes/wc-order-functions-test.php
+++ b/plugins/woocommerce/tests/php/includes/wc-order-functions-test.php
@@ -7,6 +7,7 @@
use Automattic\WooCommerce\Enums\OrderInternalStatus;
use Automattic\WooCommerce\Enums\OrderStatus;
+use Automattic\WooCommerce\Internal\Utilities\Users;
/**
* Class WC_Order_Functions_Test
@@ -541,4 +542,27 @@ class WC_Order_Functions_Test extends \WC_Unit_Test_Case {
$this->assertStringContainsString( '--', $result, "Edge case should preserve double hyphens: {$content}" );
}
}
+
+ /**
+ * Test `wc_delete_shop_order_transients`: purging user metas depending on the order state.
+ */
+ public function test_wc_delete_shop_order_transients_usermeta_purge(): void {
+ $customer = WC_Helper_Customer::create_customer();
+ $customer_id = $customer->get_id();
+ $order = WC_Helper_Order::create_order( $customer_id, null, array( 'status' => OrderStatus::COMPLETED ) );
+ $order_id = $order->get_id();
+
+ // Verify the metas getting purged for order a state different from checkout draft.
+ Users::update_site_user_meta( $customer_id, 'wc_order_count', 123 );
+ Users::update_site_user_meta( $customer_id, 'wc_last_order', 456 );
+ Users::update_site_user_meta( $customer_id, 'wc_money_spent', 789 );
+ wc_delete_shop_order_transients( $order_id );
+ $this->assertSame( '', Users::get_site_user_meta( $customer_id, 'wc_order_count' ) );
+ $this->assertSame( '', Users::get_site_user_meta( $customer_id, 'wc_last_order' ) );
+ $this->assertSame( '', Users::get_site_user_meta( $customer_id, 'wc_money_spent' ) );
+
+ // Cleanup.
+ $order->delete();
+ $customer->delete();
+ }
}