关于 wp-performance-review
用于审查 WordPress 主题、插件和自定义代码性能的技能,先检查关键风险,再按严重程度和行号整理发现。
主要功能
- 01优先扫描关键性能问题
- 02检查 PHP、JavaScript 和数据库代码
- 03覆盖 AJAX、资源、缓存与 Cron
- 04按严重程度和行号报告
适用场景
- 01审查 WordPress 主题或插件变更
- 02排查加载缓慢、超时或 500 错误
- 03高流量活动前审计代码
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ elvismdev/ claude-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-gutenberg-blocks,请使用它)
- 与 WordPress 无关的一般 PHP 代码审查
代码审查工作流
- 识别文件类型,并应用下方相关检查项
- 优先扫描关键模式(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→ 警告:排除操作缓慢 LIKE '%term%'(前置通配符)→ 警告:全表扫描- 不进行分页时缺少
no_found_rows => true→ 信息:不必要的计数
AJAX 处理程序(wp_ajax_*、REST 端点)
扫描以下内容:
- 使用
admin-ajax.php→ 信息:可考虑改用 REST API - 使用 POST 方法执行读取操作 → 警告:绕过缓存
setInterval或轮询模式 → 严重:存在自我 DDoS 风险- 缺少 nonce 验证 → 安全问题(与性能无关,但需标记)
模板文件(主题中的 *.php)
扫描以下内容:
- 在循环中使用
get_template_part→ 警告:可考虑缓存输出 - 在循环中执行数据库查询(N+1)→ 严重:查询数量倍增
- 在模板中使用
wp_remote_get→ 警告:阻塞渲染
JavaScript 文件
扫描以下内容:
- 使用
$.post(执行读取操作 → 警告:为支持缓存,请改用 GET setInterval.*fetch\|ajax→ 严重:轮询模式import _ from 'lodash'→ 警告:导入整个库会使打包文件膨胀- 页面加载时发起 AJAX 请求的内联
<script>→ 检查其必要性
区块编辑器 / Gutenberg 文件(block.json、blocks/ 中的 *.js)
扫描以下内容:
- 多次调用
registerBlockStyle()→ 警告:每次调用都会创建预览 iframe - 在渲染回调中使用
wp_kses_post($content)→ 警告:会破坏 InnerBlocks - 没有
render_callback的静态区块 → 信息:可考虑改为动态区块,以便于维护
资源注册(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常量 → INFO:Cron 在页面请求时运行 - 运行时间过长的 Cron 回调(遍历所有用户/文章)→ CRITICAL:阻塞 Cron 队列
wp_schedule_event未检查事件是否已安排 → WARNING:重复安排
快速检测的搜索模式
# Critical issues - scan these first
grep -rn "posts_per_page.*-1\|numberposts.*-1" .
grep -rn "query_posts\s*(" .
grep -rn "session_start\s*(" .
grep -rn "setInterval.*fetch\|setInterval.*ajax\|setInterval.*\\\$\." .
# Database writes on frontend
grep -rn "update_option\|add_option" . | grep -v "admin\|activate\|install"
# Uncached expensive functions
grep -rn "url_to_postid\|attachment_url_to_postid\|count_user_posts" .
# External HTTP without caching
grep -rn "wp_remote_get\|wp_remote_post\|file_get_contents.*http" .
# Cache bypass risks
grep -rn "setcookie\|session_start" .
# PHP code anti-patterns
grep -rn "in_array\s*(" . | grep -v "true\s*)" # Missing strict comparison
grep -rn "<<<" . # Heredoc/nowdoc syntax
grep -rn "cache_results.*false" .
# JavaScript bundle issues
grep -rn "import.*from.*lodash['\"]" . # Full lodash import
grep -rn "registerBlockStyle" . # Many block styles = performance issue
# Asset loading issues
grep -rn "wp_enqueue_script\|wp_enqueue_style" . | grep -v "is_page\|is_singular\|is_admin"
# Transient misuse
grep -rn "set_transient.*\\\$" . # Dynamic transient keys
grep -rn "set_transient" . | grep -v "get_transient" # Set without checking first
# WP-Cron issues
grep -rn "wp_schedule_event" . | grep -v "wp_next_scheduled" # Missing schedule check
平台背景
不同的托管环境需要采用不同的方法:
托管式 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 );
}
} );
// ❌ CRITICAL: Missing WHERE clause (falsy ID becomes 0).
$query = new WP_Query( array( 'p' => intval( $maybe_false_id ) ) );
// ✅ GOOD: Validate ID before querying.
if ( ! empty( $maybe_false_id ) ) {
$query = new WP_Query( array( 'p' => intval( $maybe_false_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: NOT IN queries (filter in PHP instead).
'post__not_in' => $excluded_ids
// ✅ GOOD: Fetch all, filter in PHP (faster for large exclusion lists).
$posts = get_posts( array( 'posts_per_page' => 100 ) );
$posts = array_filter( $posts, function( $post ) use ( $excluded_ids ) {
return ! in_array( $post->ID, $excluded_ids, true );
} );
钩子与操作
// ❌ 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
// ❌ WARNING: WP Cron runs on page requests.
// Add 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.
Transient 误用
// ❌ 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().
文章 Meta 查询
// ❌ 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
严重性定义
| 严重性 | 说明 |
|---|---|
| Critical | 会在规模增大时导致故障(OOM、500 错误、数据库锁) |
| Warning | 在高负载下会降低性能 |
| Info | 存在优化机会 |
输出格式
按以下结构整理发现:
## 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() | 绕过缓存会影响整个站点 | 始终在所有代码中搜索 session_start |
忽略非分页查询中的 no_found_rows | 虽然优化幅度不大,但影响会累积 | 标记为 INFO,而非 WARNING |
| 建议在共享托管环境中使用对象缓存 | 许多共享主机不提供持久化缓存 | 先检查托管环境 |
| 只审查 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