关于 wp-performance-review
wp-performance-review 为 WordPress 主题、插件和自定义代码提供性能审查指导,适合开发者排查慢页面、超时及数据库负载问题。它按严重级别检查查询、缓存、外部请求、资源加载等常见风险,并要求标注代码行号。
主要功能
- 01优先检查内存耗尽与无界查询
- 02检查数据库、缓存及外部请求
- 03按文件类型检查 PHP 和 JavaScript
- 04以严重级别和行号报告问题
- 05使用 rg 搜索常见反模式
适用场景
- 01审查 WordPress 代码变更
- 02调查页面变慢或请求超时
- 03高流量活动前检查性能风险
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ jorgerosal/ wordpress-skills --skill wp-performance-review安装后在 Claude Code 中输入/wp-performance-review确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/anti-patterns.mdcaching-guide.mdmeasurement-guide.mdwp-query-guide.md详细介绍
WordPress 性能审查技能
概述
对 WordPress 主题、插件和自定义代码进行系统化性能代码审查。核心原则: 先扫描严重问题(OOM、无界查询、绕过缓存),再检查警告,最后关注优化项。报告中注明行号和严重级别。
使用时机
适用于:
- 审查 WordPress 主题或插件的 PR/代码
- 用户反馈页面加载缓慢、超时或出现 500 错误
- 高流量活动(发布、促销、爆红时刻)前进行审计
- 优化 WP_Query 或数据库操作
- 调查内存耗尽或数据库锁
不适用于:
- 仅进行安全审计(如有 wp-security-review,请使用它)
- Gutenberg 区块开发模式(如有 wp-block-development,请使用它)
- 与 WordPress 无关的通用 PHP 代码审查
- 不涉及 WordPress 性能行为的产品或 UX 审查
代码审查工作流
- 识别文件类型,并应用下方相关检查项
- 优先扫描严重问题模式(OOM、无界查询、绕过缓存)
- 检查警告项(效率低下但不会造成灾难性后果)
- 记录优化项(锦上添花的改进)
- 使用下方的输出格式注明行号并报告
按文件类型检查
插件/主题 PHP 文件(functions.php、plugin.php、*.php)
扫描以下内容:
query_posts()→ 严重:禁止使用,会破坏主查询posts_per_page.*-1或numberposts.*-1→ 严重:无界查询session_start()→ 严重:绕过页面缓存add_action.*init.*或add_action.*wp_loaded→ 检查是否每次请求都会运行高开销代码- 非管理后台上下文中的
update_option或add_option→ 警告:页面加载时写入数据库 - 未使用缓存的
wp_remote_get或wp_remote_post→ 警告:阻塞式 HTTP 请求
WP_Query / 数据库代码
扫描以下内容:
- 缺少
posts_per_page参数 → 警告:默认使用博客设置中的值 - 使用
'value'比较的'meta_query'→ 警告:扫描未建立索引的列 - 用于分页/排序的大型
post__not_in数组 → 警告:可能生成开销较大的 SQL;需逐案审查 LIKE '%term%'(前导通配符)→ 警告:全表扫描- 未进行分页时缺少
no_found_rows => true→ 信息:执行了不必要的计数
AJAX 处理程序(wp_ajax_*、REST 端点)
扫描以下内容:
- 使用
admin-ajax.php→ 信息:考虑改用 REST API - 读取操作使用 POST 方法 → 警告:绕过缓存
setInterval或轮询模式 → 严重:存在自我 DDoS 风险- 缺少 nonce 验证 → 安全问题(非性能问题,但应标记)
模板文件(主题中的 *.php)
扫描以下内容:
- 循环中执行自定义查询、未缓存的
get_post_meta()或远程调用 → 警告:重复执行 / 存在 N+1 风险 - 循环中执行数据库查询(N+1)→ 严重:查询次数成倍增加
- 模板中使用
wp_remote_get→ 警告:阻塞渲染
JavaScript 文件
扫描以下内容:
- 读取操作使用
$.post(→ 警告:使用 GET 以便缓存 setInterval.*fetch\|ajax→ 严重:轮询模式import _ from 'lodash'→ 警告:完整导入库会增大打包体积- 页面加载时执行 AJAX 调用的内联
<script>→ 检查其必要性
区块编辑器 / Gutenberg 文件(block.json、blocks/ 中的 *.js)
扫描以下内容:
- 编辑器端大量获取数据或预览逻辑 → 警告:拖慢编辑器加载
- 渲染回调中的
wp_kses_post($content)→ 警告:会破坏 InnerBlocks - 大型编辑器打包文件或范围过广的导入 → 警告:增大编辑器运行时开销
资源注册(functions.php、*.php)
扫描以下内容:
wp_enqueue_script未设置版本 → 信息:缓存失效处理存在问题wp_enqueue_script未使用defer/async策略 → 信息:阻塞渲染- 缺少
THEME_VERSION常量 → 信息:版本管理问题 wp_enqueue_script未进行条件检查 → 警告:仅特定页面需要的资源却在全站加载
瞬态与选项
扫描以下内容:
- 使用动态键(例如
user_{$id})的set_transient→ 警告:没有对象缓存时会导致表膨胀 - 用于频繁变化数据的
set_transient→ 警告:违背缓存目的 - 共享主机上的大型瞬态数据 → 警告:没有对象缓存时会导致数据库膨胀
WP-Cron
扫描以下内容:
- 缺少
DISABLE_WP_CRON常量 → 信息:Cron 会在页面请求时运行 - 运行时间较长的 Cron 回调(遍历所有用户/文章) → 严重:阻塞 Cron 队列
- 使用
wp_schedule_event时未检查事件是否已计划 → 警告:重复计划
快速检测的搜索模式
# Critical issues - scan these first
rg -n "posts_per_page\s*.*-1|numberposts\s*.*-1" .
rg -n "query_posts\s*\(" .
rg -n "session_start\s*\(" .
rg -n "setInterval.*(fetch|ajax|\\$\\.)" .
# Database writes on frontend
rg -n "update_option|add_option" . -g '*.php'
# Uncached expensive functions
rg -n "url_to_postid|attachment_url_to_postid|count_user_posts" .
# External HTTP without caching
rg -n "wp_remote_get|wp_remote_post|file_get_contents\s*\(\s*['\"]https?://" .
# Cache bypass risks
rg -n "setcookie|session_start" .
# PHP code anti-patterns
rg -n "in_array\s*\(" . -g '*.php' # Manually verify strict comparison
rg -n "<<<" .
rg -n "cache_results\s*=>\s*false|cache_results\s*,\s*false" .
# JavaScript bundle issues
rg -n "import\s+_\s+from\s+['\"]lodash['\"]" . -g '*.{js,jsx,ts,tsx}'
# Asset loading issues
rg -n "wp_enqueue_script|wp_enqueue_style" . -g '*.php'
# Transient misuse
rg -n "set_transient\s*\([^,]+\\$" . -g '*.php'
rg -n "set_transient" . -g '*.php' # Manually confirm a preceding get_transient() check
# WP-Cron issues
rg -n "wp_schedule_event" . -g '*.php' # Manually confirm wp_next_scheduled() guard
平台背景
不同的托管环境需要采用不同的方法:
托管式 WordPress 主机(WP Engine、Pantheon、Pressable、WordPress VIP 等):
- 通常开箱即提供对象缓存
- 可能提供特定于平台的辅助函数(例如 VIP 上的
wpcom_vip_*) - 查看主机文档,了解推荐模式
自托管 / 标准托管:
- 手动为开销较大的函数实现对象缓存封装
- 考虑使用 Redis 或 Memcached 插件实现持久对象缓存
- 需要对缓存层配置承担更多责任
共享主机:
- 对无界查询和外部 HTTP 请求格外谨慎
- 资源有限意味着性能问题会更快显现
- 可能完全没有持久对象缓存
快速参考:关键反模式
数据库查询
// ❌ CRITICAL: Unbounded query.
'posts_per_page' => -1
// ✅ GOOD: Set reasonable limit, paginate if needed.
'posts_per_page' => 100,
'no_found_rows' => true, // Skip count if not paginating.
// ❌ CRITICAL: Never use query_posts().
query_posts( 'cat=1' ); // Breaks pagination, conditionals.
// ✅ GOOD: Use WP_Query or pre_get_posts filter.
$query = new WP_Query( array( 'cat' => 1 ) );
// Or modify main query:
add_action( 'pre_get_posts', function( $query ) {
if ( $query->is_main_query() && ! is_admin() ) {
$query->set( 'cat', 1 );
}
} );
// ❌ WARNING: Unvalidated ID can hide a logic bug and trigger needless queries.
$query = new WP_Query( array( 'p' => intval( $maybe_false_id ) ) );
// ✅ GOOD: Validate ID before querying.
$post_id = absint( $maybe_false_id );
if ( $post_id > 0 ) {
$query = new WP_Query( array( 'p' => $post_id ) );
}
// ❌ WARNING: LIKE with leading wildcard (full table scan).
$wpdb->get_results( "SELECT * FROM wp_posts WHERE post_title LIKE '%term%'" );
// ✅ GOOD: Use trailing wildcard only, or use WP_Query 's' parameter.
$wpdb->get_results( $wpdb->prepare(
"SELECT * FROM wp_posts WHERE post_title LIKE %s",
$wpdb->esc_like( $term ) . '%'
) );
// ❌ WARNING: Large NOT IN queries can become expensive, especially with pagination.
'post__not_in' => $excluded_ids
// ✅ GOOD: Prefer positive inclusion, smaller exclusion lists, or precomputed candidate IDs.
'post__in' => $candidate_ids
钩子与操作
// ❌ WARNING: Code runs on every request via init.
add_action( 'init', 'expensive_function' );
// ✅ GOOD: Check context before running expensive code.
add_action( 'init', function() {
if ( is_admin() || wp_doing_cron() ) {
return;
}
// Frontend-only code here.
} );
// ❌ CRITICAL: Database writes on every page load.
add_action( 'wp_head', 'prefix_bad_tracking' );
function prefix_bad_tracking() {
update_option( 'last_visit', time() );
}
// ✅ GOOD: Use object cache buffer, flush via cron.
add_action( 'shutdown', function() {
wp_cache_incr( 'page_views_buffer', 1, 'counters' );
} );
// ❌ WARNING: Using admin-ajax.php instead of REST API.
// Prefer: register_rest_route() - leaner bootstrap.
PHP 代码
// ❌ WARNING: O(n) lookup - use isset() with associative array.
in_array( $value, $array ); // Also missing strict = true.
// ✅ GOOD: O(1) lookup with isset().
$allowed = array( 'foo' => true, 'bar' => true );
if ( isset( $allowed[ $value ] ) ) {
// Process.
}
// ❌ WARNING: Heredoc prevents late escaping.
$html = <<<HTML
<div>$unescaped_content</div>
HTML;
// ✅ GOOD: Escape at output.
printf( '<div>%s</div>', esc_html( $content ) );
缓存问题
// ❌ WARNING: Uncached expensive function calls.
url_to_postid( $url );
attachment_url_to_postid( $attachment_url );
count_user_posts( $user_id );
wp_oembed_get( $url );
// ✅ GOOD: Wrap with object cache (works on any host).
function prefix_cached_url_to_postid( $url ) {
$cache_key = 'url_to_postid_' . md5( $url );
$post_id = wp_cache_get( $cache_key, 'url_lookups' );
if ( false === $post_id ) {
$post_id = url_to_postid( $url );
wp_cache_set( $cache_key, $post_id, 'url_lookups', HOUR_IN_SECONDS );
}
return $post_id;
}
// ✅ GOOD: On WordPress VIP, use platform helpers instead.
// wpcom_vip_url_to_postid(), wpcom_vip_attachment_url_to_postid(), etc.
// ❌ WARNING: Large autoloaded options.
add_option( 'prefix_large_data', $data ); // Add: , '', 'no' for autoload.
// ❌ INFO: Missing wp_cache_get_multiple for batch lookups.
foreach ( $ids as $id ) {
wp_cache_get( "key_{$id}" );
}
AJAX 与外部请求
// ❌ WARNING: AJAX POST request (bypasses cache).
$.post( ajaxurl, data ); // Prefer: $.get() for read operations.
// ❌ CRITICAL: Polling pattern (self-DDoS).
setInterval( () => fetch( '/wp-json/...' ), 5000 );
// ❌ WARNING: Synchronous external HTTP in page load.
wp_remote_get( $url ); // Cache result or move to cron.
// ✅ GOOD: Set timeout and handle errors.
$response = wp_remote_get( $url, array( 'timeout' => 2 ) );
if ( is_wp_error( $response ) ) {
return get_fallback_data();
}
WP Cron
// INFO: On high-traffic or cron-heavy sites, request-driven cron may not be enough.
// Consider adding to wp-config.php:
define( 'DISABLE_WP_CRON', true );
// Run via server cron: * * * * * wp cron event run --due-now
// ❌ CRITICAL: Long-running cron blocks entire queue.
add_action( 'my_daily_sync', function() {
foreach ( get_users() as $user ) { // 50k users = hours.
sync_user_data( $user );
}
} );
// ✅ GOOD: Batch processing with rescheduling.
add_action( 'my_batch_sync', function() {
$offset = (int) get_option( 'sync_offset', 0 );
$users = get_users( array( 'number' => 100, 'offset' => $offset ) );
if ( empty( $users ) ) {
delete_option( 'sync_offset' );
return;
}
foreach ( $users as $user ) {
sync_user_data( $user );
}
update_option( 'sync_offset', $offset + 100 );
wp_schedule_single_event( time() + 60, 'my_batch_sync' );
} );
// ❌ WARNING: Scheduling without checking if already scheduled.
wp_schedule_event( time(), 'hourly', 'my_task' ); // Creates duplicates!
// ✅ GOOD: Check before scheduling.
if ( ! wp_next_scheduled( 'my_task' ) ) {
wp_schedule_event( time(), 'hourly', 'my_task' );
}
缓存绕过问题
// ❌ CRITICAL: Plugin starts PHP session on frontend (bypasses ALL page cache).
session_start(); // Check plugins for this - entire site becomes uncacheable!
// ❌ WARNING: Unique query params create cache misses.
// https://example.com/?utm_source=fb&utm_campaign=123&fbclid=abc
// Each unique URL = separate cache entry = cache miss.
// Solution: Strip marketing params at CDN/edge level.
// ❌ WARNING: Setting cookies on public pages.
setcookie( 'visitor_id', $id ); // Prevents caching for that user.
滥用瞬态
// ❌ WARNING: Dynamic transient keys create table bloat (without object cache).
set_transient( "user_{$user_id}_cart", $data, HOUR_IN_SECONDS );
// 10,000 users = 10,000 rows in wp_options!
// ✅ GOOD: Use object cache for user-specific data.
wp_cache_set( "cart_{$user_id}", $data, 'user_carts', HOUR_IN_SECONDS );
// ❌ WARNING: Transients for frequently-changing data defeats purpose.
set_transient( 'visitor_count', $count, 60 ); // Changes every minute.
// ✅ GOOD: Use object cache for volatile data.
wp_cache_set( 'visitor_count', $count, 'stats' );
// ❌ WARNING: Large data in transients on shared hosting.
set_transient( 'api_response', $megabytes_of_json, DAY_IN_SECONDS );
// Without object cache = serialized blob in wp_options.
// ✅ GOOD: Check hosting before using transients for large data.
if ( wp_using_ext_object_cache() ) {
set_transient( 'api_response', $data, DAY_IN_SECONDS );
} else {
// Store in files or skip caching on shared hosting.
}
资源加载
// ❌ WARNING: Assets load globally when only needed on specific pages.
add_action( 'wp_enqueue_scripts', function() {
wp_enqueue_script( 'contact-form-js', ... );
wp_enqueue_style( 'contact-form-css', ... );
} );
// ✅ GOOD: Conditional enqueue based on page/template.
add_action( 'wp_enqueue_scripts', function() {
if ( is_page( 'contact' ) || is_page_template( 'contact-template.php' ) ) {
wp_enqueue_script( 'contact-form-js', ... );
wp_enqueue_style( 'contact-form-css', ... );
}
} );
// ✅ GOOD: Only load WooCommerce assets on shop pages.
add_action( 'wp_enqueue_scripts', function() {
if ( ! is_woocommerce() && ! is_cart() && ! is_checkout() ) {
wp_dequeue_style( 'woocommerce-general' );
wp_dequeue_script( 'wc-cart-fragments' );
}
} );
外部 API 请求
// ❌ WARNING: No timeout set (default is 5 seconds).
wp_remote_get( $url ); // Set timeout: array( 'timeout' => 2 ).
// ❌ WARNING: Missing error handling for API failures.
$response = wp_remote_get( $url );
echo $response['body']; // Check is_wp_error() first!
站点地图与重定向
// ❌ WARNING: Generating sitemaps for deep archives (crawlers hammer these).
// Solution: Exclude old post types, cache generated sitemaps.
// ❌ CRITICAL: Redirect loops consuming CPU.
// Debug with: x-redirect-by header, wp_debug_backtrace_summary().
文章元数据查询
// ❌ WARNING: Searching meta_value without index.
'meta_query' => array(
array(
'key' => 'color',
'value' => 'red',
),
)
// Better: Use taxonomy or encode value in meta_key name.
// ❌ WARNING: Binary meta values requiring value scan.
'meta_key' => 'featured',
'meta_value' => 'true',
// Better: Presence of 'is_featured' key = true, absence = false.
如需深入了解任何模式: 加载 references/anti-patterns.md
严重程度定义
| 严重程度 | 说明 |
|---|---|
| 严重 | 会在规模扩大时导致故障(OOM、500 错误、数据库锁) |
| 警告 | 高负载下会降低性能 |
| 信息 | 优化机会 |
输出格式
按以下结构整理发现:
## Performance Review: [filename/component]
### Critical Issues
- **Line X**: [Issue] - [Explanation] - [Fix]
### Warnings
- **Line X**: [Issue] - [Explanation] - [Fix]
### Recommendations
- [Optimization opportunities]
### Summary
- Total issues: X Critical, Y Warnings, Z Info
- Estimated impact: [High/Medium/Low]
常见错误
进行性能审查时,请避免以下错误:
| 错误 | 错误原因 | 修正方法 |
|---|---|---|
在仅限管理后台的代码中标记 posts_per_page => -1 | 管理后台查询不面临公共流量规模 | 检查上下文——管理后台、CLI、Cron 的风险较低 |
漏掉插件中隐藏的 session_start() | 缓存绕过会影响整个站点 | 始终在所有代码中 grep session_start |
忽略非分页查询中的 no_found_rows | 优化幅度虽小,但累积影响显著 | 标记为信息,而非警告 |
| 在共享主机上建议使用对象缓存 | 许多共享主机没有持久缓存 | 先检查托管环境 |
| 只审查 PHP,漏掉 JS 轮询 | JS setInterval + fetch = 自我 DDoS | 检查 .js 文件中的轮询模式 |
深入参考资料
根据任务加载以下参考资料:
| 任务 | 要加载的参考资料 |
|---|---|
| 审查 PHP 代码中的问题 | references/anti-patterns.md |
| 优化 WP_Query 调用 | references/wp-query-guide.md |
| 实现缓存 | references/caching-guide.md |
| 为高流量事件做准备 | references/measurement-guide.md |
注意:对于标准代码审查,anti-patterns.md 包含所需的所有模式。其他参考资料则在专门优化查询、实施缓存策略或为流量事件做准备时提供更深入的背景。
评论
支持 Markdown