mcpField
W
woocommerce-performance

woocommerce-performance

github.com/finsilabs开发6084 次安装1 周前更新
代码仓库

关于 woocommerce-performance

面向 WooCommerce 管理者和开发者,提供排查与改善商店性能的实践指导。内容包括查询分析、对象缓存、数据库优化及定期清理。

主要功能

  1. 01使用 Query Monitor 分析查询
  2. 02配置 Redis 持久对象缓存
  3. 03优化 WooCommerce 数据库查询
  4. 04清理会话与临时数据
  5. 05检查并精简自动加载选项

适用场景

  1. 01排查商店页面加载缓慢
  2. 02降低 AJAX 请求造成的数据库负载
  3. 03处理膨胀的 WooCommerce 会话表
  4. 04减少 wp_options 自动加载开销

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill woocommerce-performance

安装后在 Claude Code 中输入/woocommerce-performance确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

tile.json
evals/database-cleanup-and-session-maintenance/
criteria.json
task.md
evals/query-optimization-and-n-1-prevention/
criteria.json
task.md
evals/redis-object-cache-configuration/
criteria.json
task.md

详细介绍

WooCommerce 性能

概述

WooCommerce 性能问题通常源于五个方面:未建立索引的 meta 表导致产品查询开销高、无上限的 AJAX 购物车/结账请求、缺少持久化对象缓存、臃肿的 wp_options 自动加载数据,以及不断增长却未清理的 wp_woocommerce_sessions 和订单表。本技能涵盖性能分析、查询优化、Redis 对象缓存和定期维护流程。

何时使用本技能

  • 商店页面在中等流量下(数百名并发用户)加载缓慢时
  • WooCommerce AJAX 调用(添加到购物车、运费计算)期间服务器 CPU 使用率飙升时
  • New Relic 或 Query Monitor 显示 MySQL 查询耗时是主要瓶颈时
  • wp_options 表的自动加载数据超过 1–2 MB 时
  • wp_woocommerce_sessions 含有数百万行,导致会话查询变慢时
  • 在高流量商店中实施 Redis 或 Memcached 以减少 MySQL 负载时

核心说明

  1. 先用 Query Monitor 进行性能分析

    安装 Query Monitor 插件,找出管理后台和前端的慢查询:

    # Via WP-CLI
    wp plugin install query-monitor --activate
    

    请重点关注:

    • 每次页面加载的查询数(超过 50 个就值得关注)
    • 重复查询(同一查询多次运行)
    • 慢查询(单个查询耗时超过 100ms)
    • 大型结果集(返回数千行的查询)

    在生产环境中进行性能分析时,启用 MySQL 慢查询日志:

    SET GLOBAL slow_query_log = 'ON';
    SET GLOBAL long_query_time = 0.5;
    SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
    
  2. 启用 Redis 持久化对象缓存

    这是对 WooCommerce 影响最大的优化。如果没有它,每次页面加载都会从 MySQL 重新获取相同的产品数据:

    # Install Redis server
    sudo apt install redis-server
    
    # Install the Redis Object Cache plugin
    wp plugin install redis-cache --activate
    wp redis enable
    

    在 wp-config.php 中配置:

    define('WP_REDIS_HOST', '127.0.0.1');
    define('WP_REDIS_PORT', 6379);
    define('WP_REDIS_DATABASE', 0);
    define('WP_REDIS_TIMEOUT', 1);
    define('WP_REDIS_READ_TIMEOUT', 1);
    define('WP_REDIS_MAXTTL', 86400); // 24 hours max TTL
    
    // Selective cache groups — exclude user sessions from Redis (they change constantly)
    define('WP_REDIS_IGNORED_GROUPS', ['wc_session_id', 'counts', 'plugins']);
    
  3. 优化 WooCommerce 专用数据库查询

    <?php
    
    // Add missing indexes for common WooCommerce product queries
    // Run once via WP-CLI: wp eval-file add-indexes.php
    
    global $wpdb;
    
    // Index for product meta queries (stock status, visibility, price)
    $wpdb->query("
        ALTER TABLE {$wpdb->postmeta}
        ADD INDEX wc_product_meta_lookup (meta_key(32), meta_value(64))
    ");
    
    // Index for order meta queries
    $wpdb->query("
        ALTER TABLE {$wpdb->postmeta}
        ADD INDEX wc_order_customer_lookup (meta_key(32), meta_value(100))
    ");
    

    使用 WooCommerce 的 HPOS(高性能订单存储)将订单移出文章 meta:

    // wp-config.php — enable HPOS (WooCommerce 7.1+)
    // This is configured via WooCommerce Settings → Advanced → Features
    // Enables dedicated order tables: wp_wc_orders, wp_wc_order_items, etc.
    
    # Check HPOS status
    wp wc hpos status
    # Migrate orders to HPOS
    wp wc hpos migrate --batch-size=500
    
  4. 清理 transient、会话和日志表

    <?php
    // Scheduled cleanup — run daily via Action Scheduler
    
    add_action('my_plugin_daily_cleanup', function () {
        global $wpdb;
    
        // Delete expired WooCommerce sessions (older than 48 hours)
        $expiry_threshold = time() - (48 * HOUR_IN_SECONDS);
        $wpdb->query($wpdb->prepare("
            DELETE FROM {$wpdb->prefix}woocommerce_sessions
            WHERE session_expiry < %d
            LIMIT 5000
        ", $expiry_threshold));
    
        // Delete expired transients
        $wpdb->query("
            DELETE FROM {$wpdb->options}
            WHERE option_name LIKE '_transient_timeout_%'
              AND option_value < UNIX_TIMESTAMP()
            LIMIT 5000
        ");
        $wpdb->query("
            DELETE t FROM {$wpdb->options} t
            LEFT JOIN {$wpdb->options} e
                ON e.option_name = REPLACE(t.option_name, '_transient_', '_transient_timeout_')
            WHERE t.option_name LIKE '_transient_%'
              AND e.option_id IS NULL
            LIMIT 5000
        ");
    
        // Rotate WooCommerce logs older than 30 days
        $wpdb->query("
            DELETE FROM {$wpdb->prefix}woocommerce_log
            WHERE timestamp < DATE_SUB(NOW(), INTERVAL 30 DAY)
            LIMIT 10000
        ");
    });
    
    // Register the scheduled event
    add_action('init', function () {
        if (!as_next_scheduled_action('my_plugin_daily_cleanup')) {
            as_schedule_recurring_action(strtotime('tomorrow midnight'), DAY_IN_SECONDS, 'my_plugin_daily_cleanup');
        }
    });
    
  5. 优化 wp_options 自动加载表

    每次页面请求都会加载自动加载选项。自动加载数据臃肿会造成显著开销:

    -- Find large autoloaded options
    SELECT option_name, LENGTH(option_value) as size_bytes
    FROM wp_options
    WHERE autoload = 'yes'
    ORDER BY LENGTH(option_value) DESC
    LIMIT 30;
    
    <?php
    // Fix: Store plugin data as transients or post meta instead of autoloaded options
    
    // Bad — autoloaded, loaded on every request
    update_option('my_plugin_product_cache', $large_array, true);
    
    // Good — not autoloaded
    update_option('my_plugin_product_cache', $large_array, false);
    
    // Better — use transient with expiry
    set_transient('my_plugin_product_cache', $large_array, HOUR_IN_SECONDS);
    
    // Disable autoload for existing options
    global $wpdb;
    $wpdb->update(
        $wpdb->options,
        ['autoload' => 'no'],
        ['option_name' => 'woocommerce_attribute_taxonomies']
    );
    

示例

为产品循环添加片段缓存
<?php

// Cache rendered product card HTML in Redis to avoid re-rendering
function render_product_card_cached(int $product_id): string {
    $cache_key = "product_card_{$product_id}_" . get_locale();
    $cached = wp_cache_get($cache_key, 'product_cards');

    if ($cached !== false) {
        return $cached;
    }

    $product = wc_get_product($product_id);
    if (!$product) return '';

    ob_start();
    wc_get_template('content-product.php', ['product' => $product]);
    $html = ob_get_clean();

    // Cache for 30 minutes; invalidate on product update
    wp_cache_set($cache_key, $html, 'product_cards', 30 * MINUTE_IN_SECONDS);

    return $html;
}

// Purge cache when product is updated
add_action('woocommerce_update_product', function (int $product_id) {
    wp_cache_delete("product_card_{$product_id}_" . get_locale(), 'product_cards');
    // Also delete all locale variants
    wp_cache_flush_group('product_cards');
});
检测并修复 N+1 产品查询
<?php

// Bad — triggers N+1 queries (one per product in loop)
$product_ids = wc_get_featured_product_ids();
foreach ($product_ids as $id) {
    $product = wc_get_product($id); // <-- separate query for each product
    echo $product->get_name();
}

// Good — prime the cache before the loop
$product_ids = wc_get_featured_product_ids();
// Pre-warm the object cache with all products in a single query
$products = array_map('wc_get_product', $product_ids); // Still N queries without this:

// Better — use WC_Product_Query with ID filter (single query)
$products = wc_get_products([
    'include' => $product_ids,
    'limit'   => count($product_ids),
    'return'  => 'objects',
]);

foreach ($products as $product) {
    echo $product->get_name(); // data already loaded
}

最佳实践

  • 在 WooCommerce 7.1 及以上版本的商店中启用 HPOS(高性能订单存储)——它会将订单数据移至带有适当索引的专用表,从而消除订单方面的 wp_postmeta 瓶颈
  • 优先添加 Redis/Memcached 对象缓存——它可消除 WordPress 和 WooCommerce 每次请求都会产生的冗余 MySQL 查询,通常能将数据库负载降低 60–80%
  • 通过 Action Scheduler 在低峰时段运行清理——批量执行 DELETE 操作(每次最多处理 5000 行),避免清理期间长时间锁表
  • 仅在开发环境中将 WP_DEBUG_LOG 与 SAVEQUERIES 配合使用——在生产环境启用这些选项会严重影响性能;生产环境性能分析请使用 APM 工具(New Relic、Datadog)
  • 对管理后台的订单查询进行分页——一次加载全部订单(posts_per_page: -1)会锁定 MySQL 并导致超时;始终使用 paged,并将 posts_per_page 设为不超过 100
  • 设置 WP_REDIS_IGNORED_GROUPS,将易变数据(购物车、会话计数器)排除在 Redis 缓存之外,避免高流量结账页面出现缓存击穿
  • 每周监控 wp_options 表大小——如果自动加载数据超过 800KB,则设置告警;常见原因包括将大型数组存储为自动加载选项的插件

常见陷阱

问题解决方案
Redis 缓存未能减少 MySQL 查询在 Query Monitor 中检查 wp_cache_get 命中率——命中率低意味着缓存键失效过于频繁,或 TTL 太短
清理查询导致表级锁在 DELETE 查询中使用行级 LIMIT 子句(每次最多 5000 行),并安排在流量较低的时段运行
HPOS 迁移导致第三方插件无法正常工作启用 HPOS 前,审查所有插件中对 get_post_meta(order_id, ...) 的使用情况——这些调用必须更新为使用 $order->get_meta()
尽管有索引,商品列表仍然很慢检查 wc_lookup_table_enabled 是否已启用;WooCommerce 3.7+ 引入的 wp_wc_product_meta_lookup 表可显著加快商品查询
导入商品后对象缓存过时在批量导入脚本结束时调用 wp_cache_flush() 或 wp_cache_flush_group('products'),以使过时的商品缓存失效
清理后会话表仍然不断增长确保 WC_Session_Handler 使用数据库后端(而非 PHP 会话);默认的基于 Cookie 的会话不会创建数据库行,但自定义插件可能会强制使用数据库会话

相关技能

  • @woocommerce-plugin-development
  • @woocommerce-rest-api
  • @database-query-optimization
  • @caching-strategies
  • @infrastructure-performance

评论

支持 Markdown