This is an issue with the WooCommerce plugin. As of the 3.7.0 version the My Account > Orders page uses an inefficient query to get the customer’s orders. With 200,000 orders in the store you probably have 50x that many rows in the wp_postmeta table. I just encounterd this same issue on a site with 600K posts and 35M postmeta rows.
Source of the query
The My Account shortcode runs do_action('my_account_shortcode')
which then fires woocommerce_account_content()
.
This then fires do_action(woocommerce_account_orders_endpoint)
which calls woocommerce_accont_orders()
.
The wc_get_orders()
call on line 3050 is what is calling the query from your original question.
Potential solution
1. Change the query to get customer’s orders
To change this query I added a woocommerce_account_orders()
function to the theme that would take precidence over the WooCommerce one and called a simpler query:
global $wpdb;
$user_id = get_current_user_id();
$sql = "SELECT wp_postmeta.post_id FROM wp_postmeta WHERE wp_postmeta.meta_key = '_customer_user' AND wp_postmeta.meta_value = " . $user_id . " ORDER BY wp_postmeta.post_id DESC";
$results = $wpdb->get_results( $sql );
$orders = wp_list_pluck( $results, 'post_id' );
// Setup an array to send to the template.
$customer_orders['orders'] = $orders;
$customer_orders['max_num_pages'] = 1;
wc_get_template(
'myaccount/orders.php',
array(
'current_page' => absint( $current_page ),
'customer_orders' => $customer_orders,
'has_orders' => 0 < count( $customer_orders['orders'] ),
)
);
Possible improvements to the above code:
- I know using
$wpdb
is frowned upon, but this was a quick fix that worked to show a reduced database load from that page. - This does not page results. I tested it with a customer who had 75 orders and the page loaded in sub 1 second.
2. Override the orders template
You will also need to override the ‘myaccount/orders.php’ template in your theme and treat $customer_orders
as an array instead of an object.
In this file change
foreach ( $customer_orders->orders as $customer_order ) {
to this
foreach ( $customer_orders['orders'] as $customer_order ) {
Finally, the original query pulls posts of type ‘shop_order’ and ‘shop_order_refund’. Your store might have other order types such as subscriptions.
Inside the template loop you can add a check after wc_get_order()
such as:
// skip if this is a subscription
if ( wcs_is_subscription( $order ) ){
continue;
}