获取分类及子分类文章加载缓慢问题排查
加载缓慢的原因及优化方案
核心问题:N+1数据库查询爆炸
你的代码采用嵌套循环结构,导致数据库查询次数指数级增长:
- 先查询所有父分类,再逐个父分类查询子分类
- 接着逐个子分类查询关联文章
如果有10个父分类,每个父分类下有5个子分类,光是查文章就会触发50次数据库查询,加上分类查询,总查询量会随着分类数量急剧上升,这是加载卡顿的核心原因。
其他拖慢速度的细节
- 拉取冗余字段:
get_posts默认返回完整文章对象,但你只需要标题和链接,多余的内容、自定义字段等会增加数据库负载和内存占用。 - 加载空分类:
hide_empty设为false,会加载没有任何文章的分类,平白增加循环次数和无效查询。 - 无缓存机制:每次页面加载都重复执行所有查询,没有利用WordPress缓存功能,重复消耗服务器资源。
- 无效参数冗余:
get_posts的tax_query里多写了posts_per_page,虽不报错,但会增加查询解析的额外开销。
优化后的代码示例
<?php $taxonomy = 'categories'; $postType = 'coverage'; // 启用缓存,避免重复查询 $cache_key = 'category_post_list_' . $postType; $cached_content = get_transient($cache_key); if ($cached_content) { echo $cached_content; } else { ob_start(); // 一次性获取所有带文章的子分类(关联父分类) $child_terms = get_terms([ 'taxonomy' => $taxonomy, 'orderby' => 'term_id', 'parent' => 0, 'hide_empty' => true, // 只保留有文章的分类 'hierarchical' => false, 'fields' => 'all_with_object_id', ]); // 收集子分类ID,批量查询所有关联文章 $child_term_ids = wp_list_pluck($child_terms, 'term_id'); $posts_query = new WP_Query([ 'post_status' => 'publish', 'post_type' => $postType, 'posts_per_page' => -1, 'tax_query' => [ [ 'taxonomy' => $taxonomy, 'field' => 'term_id', 'terms' => $child_term_ids, ], ], 'fields' => 'ids', // 只获取文章ID,减少数据传输 ]); // 将文章按分类分组 $posts_by_term = []; if ($posts_query->have_posts()) { foreach ($posts_query->posts as $post_id) { $terms = wp_get_post_terms($post_id, $taxonomy, ['fields' => 'ids']); foreach ($terms as $term_id) { if (in_array($term_id, $child_term_ids)) { $posts_by_term[$term_id][] = $post_id; } } } } // 按父分类分组子分类 $parent_terms = []; foreach ($child_terms as $term) { $parent_terms[$term->parent][] = $term; } // 输出内容 foreach ($parent_terms as $parent_id => $terms) { $parent_term = get_term($parent_id, $taxonomy); echo '<div class="location-category" data-aos="fade-up"><h3>' . esc_html($parent_term->name) . '<i class="fa-regular fa-circle-right"></i></h3>'; foreach ($terms as $child_term) { echo '<div class="category-subs">'; echo '<p class="category-tl"><i class="fa-regular fa-circle-down"></i> ' . esc_html($child_term->name) . '</p>'; if (!empty($posts_by_term[$child_term->term_id])) { foreach ($posts_by_term[$child_term->term_id] as $post_id) { echo '<a href="' . esc_url(get_permalink($post_id)) . '" class="category-bl"><i class="fa-regular fa-circle-right"></i> ' . esc_html(get_the_title($post_id)) . '</a>'; } } echo '</div>'; } echo '</div>'; } wp_reset_postdata(); // 缓存内容1小时(可按需调整时长) $content = ob_get_clean(); set_transient($cache_key, $content, 3600); echo $content; } ?>
优化点说明
- 批量查询替代嵌套查询:一次性获取所有子分类和关联文章,再通过PHP分组,把数据库查询次数从几十次降到3-4次。
- 仅拉取必要字段:用
fields => 'ids'获取文章ID,再通过get_permalink和get_the_title获取所需内容,减少数据传输和内存占用。 - 启用缓存机制:用
transient缓存整个输出内容,1小时内重复访问无需再次查询数据库。 - 过滤空分类:
hide_empty => true跳过无文章分类,减少无效循环。 - 安全输出处理:用
esc_html和esc_url处理输出内容,避免XSS漏洞(原代码未做安全处理,顺便补上)。
内容的提问来源于stack exchange,提问作者Brad
相关产品推荐
相关产品推荐

