作為一個(gè) WordPress 插件,WooCommerce 早起很自然的使用了自定義文章類型來(lái)存儲(chǔ)商品和訂單數(shù)據(jù),當(dāng) WooCommerce 的邏輯越來(lái)越完善后,自定義文章類型的數(shù)據(jù)結(jié)構(gòu)就帶來(lái)了越來(lái)越嚴(yán)重的性能問(wèn)題。 WooCommerce HPOS (High-Performance Order Storage) 就是 WooCommerce 團(tuán)隊(duì)為了解決WooCommerce 性能問(wèn)題而開(kāi)發(fā)的解決方案。
HPOS 究竟是什么?
HPOS(High-Performance Order Storage)是在數(shù)據(jù)庫(kù)中存儲(chǔ) WooCommerce 訂單的一種更高性能的存儲(chǔ)的方式。自 8.2 版本起,新創(chuàng)建的商店默認(rèn)就開(kāi)啟了 HPOS。以前,訂單只是一個(gè)自定義文章類型shop_order,訂單數(shù)據(jù)存儲(chǔ)在wp_posts和wp_postmeta表中,但現(xiàn)在我們有一堆專門用于 WooCommerce 訂單的數(shù)據(jù)庫(kù)表。
數(shù)據(jù)庫(kù)結(jié)構(gòu)
接下來(lái),我們看一下 HPOS 的數(shù)據(jù)結(jié)構(gòu),以及幾個(gè)示例,通過(guò)這幾個(gè)示例,大家可以了解到 HPOS 的存儲(chǔ)方式為什么會(huì)比較高效。
實(shí)際上,我們需要關(guān)注的主有三個(gè)數(shù)據(jù)表。
首先是wp_wc_orders– 它存儲(chǔ)了主要的訂單信息。

第二個(gè)是wp_wc_order_addresses– 這個(gè)數(shù)據(jù)表里面存儲(chǔ)了賬單和配送地址信息。

第三個(gè)數(shù)據(jù)表是 wp_wc_order_operational_data– 這是對(duì)主要訂單信息表的補(bǔ)充。,些字段與訂單狀態(tài)有關(guān),并保存在一個(gè)單獨(dú)的表中,因?yàn)轭A(yù)計(jì)該表的更改會(huì)更頻繁。

第四個(gè)數(shù)據(jù)是 wp_wc_orders_meta(等等,你說(shuō)只有三個(gè))。這個(gè)表并沒(méi)有完全投入使用,但它與wp_postmeta有點(diǎn)類似,可以被其他插件使用。

有了上面的數(shù)據(jù)表,從數(shù)據(jù)庫(kù)中獲取并顯示訂單的過(guò)程會(huì)更加高效,下面讓我們看一個(gè)簡(jiǎn)單的 SQL 查詢,我們需要按賬單國(guó)家獲取訂單。
在基于自定義文章類型存儲(chǔ)訂單數(shù)據(jù)的商店中,我們需要這樣查詢數(shù)據(jù):
SELECT wp_posts.ID
FROM wp_posts
INNER JOIN wp_postmeta AS country ON wp_posts.ID = country.post_id AND meta_key = '_billing_country'
WHERE wp_posts.post_type = 'shop_order'
AND country.meta_value = 'DK'
在基于 HPOS 存儲(chǔ)訂單數(shù)據(jù)的商店中,獲取同樣數(shù)據(jù)的查詢變成了下面的語(yǔ)句:
SELECT wp_wc_orders.id
FROM wp_wc_orders
INNER JOIN wp_wc_order_addresses AS address ON address.order_id = wp_wc_orders.id AND address.address_type = 'billing'
WHERE address.country = 'DK'
如果您的數(shù)據(jù)庫(kù)中有 5000 個(gè)左右的訂單,那么最后一個(gè)查詢將快 4 倍!換句話說(shuō),您商店中的訂單越多,HPOS 的性能就越高。其中一個(gè)原因是–我們不必再處理臃腫的wp_posts和wp_postmeta(尤其是wp_postmeta)。
如何開(kāi)啟 HPOS?
長(zhǎng)話短說(shuō),您只需進(jìn)入WooCommmerce > 設(shè)置 > 高級(jí) > 功能,然后切換到基于 HPOS 的訂單就可以了。

但有一些細(xì)微差別需要注意:
- 自 WooCommerce 8.2 版起,所有新店默認(rèn)啟用 HPOS。
- 如果您的插件不兼容,則無(wú)法切換到 HPOS。
- 如果商店中有未同步的訂單,則無(wú)法切換到 HPOS 或返回 HPOS(您可以順便刪除這些訂單,或直接點(diǎn)擊設(shè)置下方的 “同步 X 待處理訂單 “鏈接)。
- 還有一個(gè) “兼容性模式 “復(fù)選框,其基本作用是同時(shí)使用高性能訂單存儲(chǔ)和基于 CPT 的訂單存儲(chǔ)(是的,它會(huì)復(fù)制訂單,這對(duì)性能完全不利,但對(duì)兼容性非常有利)。
使您的插件與 HPOS 兼容
這是一個(gè)值得討論的有趣話題。我可以肯定,很多人都在使用WP_Query來(lái)獲取 WooCommerce 訂單或商品數(shù)據(jù),如果切換到了 HPOS 存儲(chǔ)方式, 舊的獲取數(shù)據(jù)的代碼就會(huì)失效。但是,如果您從一開(kāi)始就使用 CRUD 方法與 WooCommerce 訂單進(jìn)行交互,那么您可能根本不需要修改代碼中的任何內(nèi)容(我沒(méi)有修改過(guò)我的插件中的任何一行 – 一切都和以前一樣)就可以兼容 HPOS。
總之,讓我們來(lái)看看我們必須牢記的幾個(gè)要點(diǎn)。
代碼更改示例
這里的關(guān)鍵是停止使用 WordPress 相關(guān)的文章查詢函數(shù),開(kāi)始使用基于 WooCommerce CRUD 的函數(shù)、類及其方法。下面是一些示例。
獲取訂單信息:
// 停止使用這種方式:
// $post = get_post( $post_id ); // 返回 WP_Post 對(duì)象
// $order_status = $post->post_status;
// 使用這種方式:
$order = wc_get_order( $post_id ); // 返回 WC_Order 對(duì)象
$order_status = $order->get_status();
使用 wc_get_orders 函數(shù)替代 WP_Query 類獲取訂單:
// 停止使用這種方式:
// $query = new WP_Query( array( 'post_type' => 'shop_order', 'posts_per_page' => 10 ) );
// 使用這種方式:
$query = new WC_Order_Query( array( 'limit' => 10 ) );
$orders = $query->get_orders();
// 停止使用這種方式:
// $orders = get_posts( array( 'post_type' => 'shop_order', 'posts_per_page' => 10 ) );
// 使用這種方式:
$orders = wc_get_orders( array( 'limit' => 10 ) );
獲取和更新訂單元數(shù)據(jù):
// 停止使用這種方式:
// $custom_meta = get_post_meta( $order_id, '_misha_key', true );
// $custom_meta = $custom_meta .= 'test';
// update_post_meta( $order_id, '_misha_key', $custom_meta );
// 該用這種方式:
$order = wc_get_order( $order_id );
$custom_meta = $order->get_meta( '_misha_key', true );
$custom_meta = $custom_meta .= 'test';
$order->update_meta_data( '_misha_key', $custom_meta );
$order->save();
檢查文章類型:
// 停止使用這種方式
// if( 'shop_order' === get_post_type( $post_id ) ) {
// 該用這種方式:
use Automattic\WooCommerce\Utilities\OrderUtil; // at the beginning of the file
if( 'shop_order' === OrderUtil::get_order_type( $post_id ) ) {
但由于 WooCommerce 會(huì)創(chuàng)建與 ID 匹配的訂單占位符,因此也可以通過(guò)以下方式進(jìn)行檢查:'shop_order_placehold' === get_post_type( $post_id ),而無(wú)需使用OrderUtil類。
訂單占位符
需要注意的一個(gè)有趣的向后兼容性問(wèn)題是,每個(gè) HPOS 訂單都有自己的 “訂單占位符”,即在wp_posts表中創(chuàng)建的自定義文章類型shop_order_placehold。它包含實(shí)際訂單 ID,但數(shù)據(jù)庫(kù)行中幾乎所有其他列都是空的。wp_postmeta表中也沒(méi)有寫入任何內(nèi)容。

檢查是否啟用了 HPOS
官方文檔建議使用OrderUtil類來(lái)執(zhí)行檢查。
use Automattic\WooCommerce\Utilities\OrderUtil;
if( OrderUtil::custom_orders_table_usage_is_enabled() ) {
// HPOS 已啟用.
} else {
// 正在使用基于文章類型的訂單
}
在插件中聲明兼容性
好消息是,如果我們因?yàn)槟撤N原因無(wú)法為自定義插件添加 HPOS 支持,我們可以讓 WooCommerce 知道這一點(diǎn)。
下面是一個(gè)與高性能訂單存儲(chǔ)不兼容的插件示例:
<?php
/*
* Plugin name: HPOS compatibility check
* Version: 1.0
* Author: Misha Rudrastyh
* Author URI: https://rudrastyh.com
*/
add_action( 'before_woocommerce_init', 'wprs_hpos_compatibility' );
function wprs_hpos_compatibility() {
if( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) {
\Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility(
'custom_order_tables',
__FILE__,
false // true (compatible, default) or false (not compatible)
);
}
}
目前沒(méi)有任何措施阻止您在 HPOS 商店中激活類似插件,但如果您已經(jīng)有一個(gè)激活了該插件的商店,您就不能在設(shè)置中切換到 HPOS。
您不能在激活了不兼容插件的情況下切換到 “高性能訂單存儲(chǔ)”。
屏幕 ID 更改
由于訂單不再是文章類型(但仍使用相同的WP_List_Table類),因此我們不能在條件中使用標(biāo)準(zhǔn)的文章類型屏幕 ID。新的屏幕 ID 是woocommerce_page_wc-orders。好了,讓我給你們舉幾個(gè)例子。
在第一個(gè)例子中,我們只是使用admin_notices操作鉤子來(lái)打印一些內(nèi)容。
add_action( 'admin_notices', 'wprs_woo_notice_example' );
function wprs_woo_notice_example() {
$screen = get_current_screen();
// 基于文章類型的訂單中,我們這樣判斷:
// if( 'edit-shop_order' === $screen->id ) {
if( 'woocommerce_page_wc-orders' === $screen->id ) {
echo '<div class="notice notice-info"><p>Hey there</p></div>';
}
}
可以正常判斷:

在第二個(gè)示例中,我們將在訂單表中添加一個(gè)自定義列。列鉤子使用屏幕 ID 作為鉤子名稱的一部分。
// 之前我們像這樣使用 manage_edit-{POST TYPE}_columns 鉤子:
// add_filter( 'manage_edit-shop_order_columns', function( $columns ){
add_filter( 'manage_woocommerce_page_wc-orders_columns', function( $columns ) {
$columns[ 'misha_column' ] = 'Info from Misha';
return $columns;
} );
// 之前的使用方式
// add_action( 'manage_posts_custom_column', function( $column_name, $order_id ){
add_action( 'manage_woocommerce_page_wc-orders_custom_column', function( $column, $order ){
if( 'misha_column' === $column ) {
echo 'hi there';
}
}, 25, 2 );
請(qǐng)注意,在第二個(gè)鉤子中,我們不僅更改了鉤子名稱,還將整個(gè)WC_Order對(duì)象作為第二個(gè)參數(shù),因此我們可用$order->get_id()或其他參數(shù)。之前我們只有$order_id可用。
對(duì)了,這里還有一列:

另一個(gè)例子–我們需要為 “編輯訂單 “頁(yè)面創(chuàng)建一個(gè)自定義元數(shù)據(jù)盒子。在創(chuàng)建元數(shù)據(jù)盒子時(shí),我們不能像以前那樣在add_meta_box()函數(shù)中提供shop_order值。相反,我們必須使用wc_get_page_screen_id( 'shop-order' )函數(shù)。示例如下:
add_action( 'add_meta_boxes', function() {
// before we used
// add_meta_box( 'misha', 'Meta Box', 'misha_metabox', 'shop_order' );
add_meta_box( 'misha', 'Meta Box', 'misha_metabox', wc_get_page_screen_id( 'shop-order' ) );
} );
function misha_metabox( $order ) { // WC_Order object is available here
echo 'hey, this is an order with ID ' . $order->get_id();
}
顯示效果如下:

當(dāng)然,我們也可以多做一些工作,讓這個(gè)功能在基于自定義文章類型的訂單和基于 HPOS 的訂單中都能正常工作。
use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
add_action( 'add_meta_boxes', function() {
$screen = wc_get_container()->get( CustomOrdersTableController::class )->custom_orders_table_usage_is_enabled()
? wc_get_page_screen_id( 'shop-order' )
: 'shop_order';
add_meta_box( 'misha', 'Hey', 'misha_metabox', $screen );
...
不要忘記,回調(diào)函數(shù)的主要參數(shù)是$object_id或$order,這取決于 HPOS 是否激活。因此,我們還必須這樣做:
function misha_metabox( $order_or_post_id ) {
$order = ( $order_or_post_id instanceof WP_Post )
? wc_get_order( $order_or_post_id->ID )
: $order_or_post_id;
echo 'hey, this is an order with ID ' . $order->get_id();
上面就是我們總結(jié)的 WooCommerce 高性能訂單存儲(chǔ)的方方面面,希望對(duì)大家有所幫助。如果你在使用 WooCommerce HPOS 的過(guò)程中遇到了問(wèn)題,歡迎在評(píng)論中提出,我們一起討論。


