作为一个 WordPress 插件,WooCommerce 早起很自然的使用了自定义文章类型来存储商品和订单数据,当 WooCommerce 的逻辑越来越完善后,自定义文章类型的数据结构就带来了越来越严重的性能问题。 WooCommerce HPOS (High-Performance Order Storage) 就是 WooCommerce 团队为了解决WooCommerce 性能问题而开发的解决方案。
HPOS 究竟是什么?
HPOS(High-Performance Order Storage)是在数据库中存储 WooCommerce 订单的一种更高性能的存储的方式。自 8.2 版本起,新创建的商店默认就开启了 HPOS。以前,订单只是一个自定义文章类型shop_order
,订单数据存储在wp_posts
和wp_postmeta
表中,但现在我们有一堆专门用于 WooCommerce 订单的数据库表。
数据库结构
接下来,我们看一下 HPOS 的数据结构,以及几个示例,通过这几个示例,大家可以了解到 HPOS 的存储方式为什么会比较高效。
实际上,我们需要关注的主有三个数据表。
首先是wp_wc_orders
– 它存储了主要的订单信息。
第二个是wp_wc_order_addresses
– 这个数据表里面存储了账单和配送地址信息。
第三个数据表是wp_wc_order_operational_data
– 这是对主要订单信息表的补充。,些字段与订单状态有关,并保存在一个单独的表中,因为预计该表的更改会更频繁。
第四个数据是 wp_wc_orders_meta
(等等,你说只有三个)。这个表并没有完全投入使用,但它与wp_postmeta
有点类似,可以被其他插件使用。
有了上面的数据表,从数据库中获取并显示订单的过程会更加高效,下面让我们看一个简单的 SQL 查询,我们需要按账单国家获取订单。
在基于自定义文章类型存储订单数据的商店中,我们需要这样查询数据:
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 存储订单数据的商店中,获取同样数据的查询变成了下面的语句:
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'
如果您的数据库中有 5000 个左右的订单,那么最后一个查询将快 4 倍!换句话说,您商店中的订单越多,HPOS 的性能就越高。其中一个原因是–我们不必再处理臃肿的wp_posts
和wp_postmeta
(尤其是wp_postmeta
)。
如何开启 HPOS?
长话短说,您只需进入WooCommmerce > 设置 > 高级 > 功能,然后切换到基于 HPOS 的订单就可以了。
但有一些细微差别需要注意:
- 自 WooCommerce 8.2 版起,所有新店默认启用 HPOS。
- 如果您的插件不兼容,则无法切换到 HPOS。
- 如果商店中有未同步的订单,则无法切换到 HPOS 或返回 HPOS(您可以顺便删除这些订单,或直接点击设置下方的 “同步 X 待处理订单 “链接)。
- 还有一个 “兼容性模式 “复选框,其基本作用是同时使用高性能订单存储和基于 CPT 的订单存储(是的,它会复制订单,这对性能完全不利,但对兼容性非常有利)。
使您的插件与 HPOS 兼容
这是一个值得讨论的有趣话题。我可以肯定,很多人都在使用WP_Query
来获取 WooCommerce 订单或商品数据,如果切换到了 HPOS 存储方式, 旧的获取数据的代码就会失效。但是,如果您从一开始就使用 CRUD 方法与 WooCommerce 订单进行交互,那么您可能根本不需要修改代码中的任何内容(我没有修改过我的插件中的任何一行 – 一切都和以前一样)就可以兼容 HPOS。
总之,让我们来看看我们必须牢记的几个要点。
代码更改示例
这里的关键是停止使用 WordPress 相关的文章查询函数,开始使用基于 WooCommerce CRUD 的函数、类及其方法。下面是一些示例。
获取订单信息:
// 停止使用这种方式:
// $post = get_post( $post_id ); // 返回 WP_Post 对象
// $order_status = $post->post_status;
// 使用这种方式:
$order = wc_get_order( $post_id ); // 返回 WC_Order 对象
$order_status = $order->get_status();
获取订单:
// 停止使用这种方式:
// $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 ) );
获取和更新订单元数据:
// 停止使用这种方式:
// $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 会创建与 ID 匹配的订单占位符,因此也可以通过以下方式进行检查:'shop_order_placehold' === get_post_type( $post_id )
,而无需使用OrderUtil
类。
订单占位符
需要注意的一个有趣的向后兼容性问题是,每个 HPOS 订单都有自己的 “订单占位符”,即在wp_posts
表中创建的自定义文章类型shop_order_placehold
。它包含实际订单 ID,但数据库行中几乎所有其他列都是空的。wp_postmeta
表中也没有写入任何内容。
检查是否启用了 HPOS
官方文档建议使用OrderUtil
类来执行检查。
use Automattic\WooCommerce\Utilities\OrderUtil;
if( OrderUtil::custom_orders_table_usage_is_enabled() ) {
// HPOS 已启用.
} else {
// 正在使用基于文章类型的订单
}
在插件中声明兼容性
好消息是,如果我们因为某种原因无法为自定义插件添加 HPOS 支持,我们可以让 WooCommerce 知道这一点。
下面是一个与高性能订单存储不兼容的插件示例:
<?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)
);
}
}
目前没有任何措施阻止您在 HPOS 商店中激活类似插件,但如果您已经有一个激活了该插件的商店,您就不能在设置中切换到 HPOS。
您不能在激活了不兼容插件的情况下切换到 “高性能订单存储”。
屏幕 ID 更改
由于订单不再是文章类型(但仍使用相同的WP_List_Table
类),因此我们不能在条件中使用标准的文章类型屏幕 ID。新的屏幕 ID 是woocommerce_page_wc-orders
。好了,让我给你们举几个例子。
在第一个例子中,我们只是使用admin_notices
操作钩子来打印一些内容。
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>';
}
}
可以正常判断:
在第二个示例中,我们将在订单表中添加一个自定义列。列钩子使用屏幕 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 );
请注意,在第二个钩子中,我们不仅更改了钩子名称,还将整个WC_Order
对象作为第二个参数,因此我们可用$order->get_id()
或其他参数。之前我们只有$order_id
可用。
对了,这里还有一列:
另一个例子–我们需要为 “编辑订单 “页面创建一个自定义元数据盒子。在创建元数据盒子时,我们不能像以前那样在add_meta_box()
函数中提供shop_order
值。相反,我们必须使用wc_get_page_screen_id( 'shop-order' )
函数。示例如下:
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();
}
显示效果如下:
当然,我们也可以多做一些工作,让这个功能在基于自定义文章类型的订单和基于 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 );
...
不要忘记,回调函数的主要参数是$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();
上面就是我们总结的 WooCommerce 高性能订单存储的方方面面,希望对大家有所帮助。如果你在使用 WooCommerce HPOS 的过程中遇到了问题,欢迎在评论中提出,我们一起讨论。