如何优化WP REST API拉取CPT+ACF树形关联数据的性能
性能问题根因
你当前实现慢的核心原因是逐节点发起独立HTTP REST请求,属于典型的N+1查询反模式。
你现在拉取3代血统数据,算上根节点总共需要发起15次独立HTTP请求:每次wp_remote_get调用都要完成TCP握手、Web服务器接收请求、WordPress全流程初始化、数据库查询、REST接口响应、JSON解码的完整流程,单次请求就算耗时3秒,15次叠加后总耗时就会达到45秒以上,和你观测到的52秒加载时间完全吻合。
另外你现有代码还有个逻辑bug:$tree变量定义后从未写入数据,最终返回的是空数组。
优化方案(按收益优先级排序)
1. 优先去掉HTTP自调用,直接使用WP内部函数查询(性能提升99%以上,耗时可降到200ms内)
如果你的家谱逻辑和WordPress站点部署在同一套代码环境下,完全没必要走REST接口请求自身站点的数据,直接调用WordPress/ACF内置函数读取数据即可,直接省掉所有HTTP请求开销。
同时增加内存级静态缓存,同一次请求中已经查询过的马匹数据直接从内存读取,避免重复查库:
/** * 内存缓存已查询过的马匹父母数据,同一次请求不重复查库 */ function &get_horse_static_cache() { static $cache = []; return $cache; } /** * 直接读取ACF字段获取指定马匹的父母ID,不走REST接口 * @param int $horse_id 马匹文章ID * @return array 包含dad、mom两个ID的数组 */ function get_horse_parents(int $horse_id): array { $cache = &get_horse_static_cache(); if (isset($cache[$horse_id])) { return $cache[$horse_id]; } // 第三个参数传false,直接返回关联字段的原始ID,不加载完整文章对象,进一步降低开销 $dad_id = get_field('dad', $horse_id, false); $mom_id = get_field('mom', $horse_id, false); $cache[$horse_id] = [ 'dad' => $dad_id ? intval($dad_id) : null, 'mom' => $mom_id ? intval($mom_id) : null ]; return $cache[$horse_id]; } /** * 递归生成指定深度的血统树 * @param int $horse_id 根节点马匹ID * @param int $max_depth 最多查询几代,默认3代 * @param int $current_depth 当前递归深度 * @param array $visited 已访问ID列表,避免循环关联导致死递归 * @return array|null */ function fetch_family_tree(int $horse_id, int $max_depth = 3, int $current_depth = 1, array $visited = []) { if (empty($horse_id) || in_array($horse_id, $visited) || $current_depth > $max_depth) { return null; } $visited[] = $horse_id; $parents = get_horse_parents($horse_id); return [ 'self_id' => $horse_id, 'dad' => fetch_family_tree($parents['dad'], $max_depth, $current_depth + 1, $visited), 'mom' => fetch_family_tree($parents['mom'], $max_depth, $current_depth + 1, $visited) ]; }
如果你需要和原有代码一致的i/ii/ie这类扁平化键名,只需要在生成树形结构后做一次映射即可,核心是不要逐节点发起HTTP请求。
2. 必须走REST接口的场景:批量拉取+持久化缓存
如果你的业务代码和WordPress站点分离,必须通过REST接口取数,做两个优化即可把耗时降到秒级:
- 不要逐ID发请求,先递归收集所有需要查询的马匹ID,一次性调用REST列表接口批量拉取:请求格式为
/wp-json/wp/v2/horse?include[]=ID1&include[]=ID2&per_page=100,3代血统只需要1次HTTP请求即可拉完所有数据 - 增加持久化缓存:把生成好的血统树存在WordPress transient缓存里,设置24小时过期,在马匹文章更新时自动清除对应缓存,不用每次用户访问页面都重新拉取生成,缓存实现参考:
function get_cached_family_tree(int $horse_id, int $max_depth = 3) { $cache_key = 'horse_pedigree_' . $horse_id . '_' . $max_depth; $cached = get_transient($cache_key); if ($cached !== false) { return $cached; } // 这里放你批量拉取数据、生成血统树的逻辑 $tree = fetch_family_tree($horse_id, $max_depth); // 缓存24小时 set_transient($cache_key, $tree, 86400); // 马匹文章保存更新时,自动清除对应血统树缓存 add_action('save_post_horse', function(int $post_id) { global $wpdb; // 清除该马匹相关的所有血统缓存 $wpdb->query($wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s", '_transient_horse_pedigree_' . $post_id . '_%' )); }); return $tree; }
其他优化细节
- 注意你原有代码里的字段名不一致问题:你描述ACF自定义字段为
mom/dad,但辅助函数里读取的是ema字段,记得统一命名避免取空值 - 如果需要查询超过5代的血统数据,可以给ACF关联字段的meta_key加数据库索引,查询速度会有明显提升
- 递归查询时一定要加循环引用判断,避免后台误操作把马匹的父母设成自己的后代导致死递归耗尽内存
内容的提问来源于stack exchange,提问作者Hidieh
相关产品推荐
相关产品推荐

