搜索全站
主题 11 分钟阅读

WordPress 评论区加载慢:评论分页与头像缓存的落地做法

一条热门文章几百条评论,评论区渲染拖慢整页。本文讲清评论查询与头像外链的成本构成,给出后台分页设置、列表参数调整、头像与列表片段缓存的可执行方案。

U
UFO_PF 2021-02-03 · 阅读 11 分钟
Wo 主题
阅读提示
本文约 4,318 字,预计阅读 11 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:五百条评论,把一篇爆款文章拖到五秒

做内容运营的站点常遇到这种尴尬:某篇文章突然火了,评论区攒到五六百条,前台打开这篇文章要等四五秒,而其他文章一切正常。后台看着没报错,服务器负载也不高,问题就出在评论区——comments_template() 一次性把这篇文章的全部评论查出来,逐条走 wp_list_comments() 渲染,每条评论还要为头像单独拼一次 Gravatar 地址。评论数越多,这个过程越接近线性增长。

更隐蔽的是头像。WordPress 默认从 Gravatar 拉取头像,国内访问这个域名延迟很高,一条评论一个请求,五百条就是五百个外部请求。访客看到的是「文字先出来、头像一格一格慢慢填」,体验非常割裂。很多站长的做法是干脆关掉头像显示,这是因噎废食;正确思路是三件事一起做:后台开启评论分页、调整列表渲染参数、给头像和列表加缓存。下面按这个顺序讲。

原理分析:评论区的三条成本线

第一是查询成本。评论存储在 wp_comments 表,取数条件主要是 comment_post_ID + comment_approved,未建合适索引时大表扫描很明显。wp_comments 默认对 comment_post_ID 有索引,但配合 comment_approved 和排序时,仍可能触发 filesort。

第二是渲染成本。wp_list_comments() 默认走嵌套(threaded)模式,需要递归排序,层级越深递归次数越多。后台「设置 → 讨论」里的嵌套层级默认可到 5 层,热门文章里这种深度很常见。

第三是外部请求成本。头像由 get_avatar() 生成,URL 指向 secure.gravatar.com,每个 src 都是一次独立 HTTP 请求,且不受站点缓存控制。

成本来源 触发点 典型表现 处理手段
评论查询 comments_template() SQL 耗时长、查询数不降 后台开启分页
嵌套排序 wp_list_comments() PHP 执行时间上升 限制嵌套层级
头像外链 get_avatar() 头像逐格加载 换镜像 + 本地缓存
重复渲染 每次请求重跑 缓存命中也慢 片段缓存评论列表

评论相关的后台开关都在「设置 → 讨论」里:分页显示评论、每页条数、显示顺序、嵌套层级。这些选项对应 wp_options 里的 page_comments、comments_per_page、comment_order、thread_comments_depth,批量站点可以直接改库统一配置。相关数据结构与索引处理可参考 WordPress 数据库表结构说明。

保姆级实操

  1. 开启评论分页:后台「设置 → 讨论」勾选「分页显示评论」,每页设为 20~30 条,并勾选「由评论较多的最后一页开始显示」。这一步立竿见影,把单页评论渲染量固定下来。
  2. 限制嵌套层级:同一页把「启用嵌套(分级)评论」的层级设为 2~3 层。层级越深,递归排序和输出缩进的成本越高,三层已经足够承载绝大多数讨论。
  3. 统一列表参数:用 wp_list_comments_args 过滤器固定 avatar_size,避免主题在不同位置传不同尺寸导致头像无法复用缓存。
  4. 头像缓存:用 get_avatar 过滤器把渲染结果按「评论 ID + 尺寸」缓存进 transient,热门文章第二次访问直接命中。
  5. 评论列表片段缓存:对不常变动的旧文章,把整个评论列表按「文章 ID + 评论页码」缓存,新评论提交时定向清理该文章的所有缓存。
  6. 验证:用 Query Monitor 对比改造前后的「Peak Memory Usage」与 SQL 条数,同时用浏览器开发者工具的网络面板确认头像请求数是否下降。
<?php
// 子主题 functions.php:固定评论列表参数
add_filter( 'wp_list_comments_args', function( $args ) {
    $args['avatar_size'] = 48;   // 全站统一,便于缓存复用
    $args['style']       = 'ol';
    $args['short_ping']  = true; // 引用通告不输出完整内容
    $args['max_depth']   = 3;
    $args['per_page']    = 20;
    return $args;
} );
<?php
// 头像片段缓存:按评论 ID + 尺寸缓存 30 天
add_filter( 'get_avatar', function( $avatar, $id_or_email, $size ) {
    if ( ! is_object( $id_or_email ) || empty( $id_or_email->comment_ID ) ) {
        return $avatar;
    }
    $key  = 'mfav_' . (int) $id_or_email->comment_ID . '_' . (int) $size;
    $html = get_transient( $key );
    if ( false === $html ) {
        $html = $avatar;
        set_transient( $key, $html, 30 * DAY_IN_SECONDS );
    }
    return $html;
}, 10, 3 );
<?php
// 评论列表片段缓存 + 新评论时定向清理
function mimangfei_comments_cache_key( $post_id, $cpage ) {
    return 'mfcm_' . (int) $post_id . '_' . (int) $cpage;
}

add_action( 'wp_insert_comment', 'mimangfei_purge_post_comments_cache' );
add_action( 'transition_comment_status', 'mimangfei_purge_post_comments_cache' );
function mimangfei_purge_post_comments_cache( $comment_id ) {
    $comment = get_comment( $comment_id );
    if ( ! $comment ) {
        return;
    }
    global $wpdb;
    $like = $wpdb->esc_like( 'mfcm_' . $comment->comment_post_ID . '_' ) . '%';
    $wpdb->query( $wpdb->prepare(
        "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s",
        '_transient_' . $like,
        '_transient_timeout_' . $like
    ) );
    clean_post_cache( $comment->comment_post_ID );
}
-- 批量站点直接改配置:每页 20 条、开启分页、倒序
UPDATE wp_options SET option_value = '20'  WHERE option_name = 'comments_per_page';
UPDATE wp_options SET option_value = '1'   WHERE option_name = 'page_comments';
UPDATE wp_options SET option_value = 'desc' WHERE option_name = 'comment_order';
UPDATE wp_options SET option_value = '3'   WHERE option_name = 'thread_comments_depth';

常见踩坑

  • 只做头像缓存不做分页:查询和渲染的大头还在,热门文章依旧慢。分页必须先做。
  • 缓存键不含页码:评论翻页后看到的是第一页内容,访客会以为评论丢了。
  • 清理时全库 wp_cache_flush():一次新评论把整站缓存清空,反而造成短时间雪崩。按文章 ID 定向清理。
  • 嵌套层级设成 10:后台允许,但递归深度上去之后,输出层级和内存占用都明显变高。
  • per_page 与后台设置不一致:过滤器里写 20、后台写 50,两个数字打架,翻页数和后台预览对不上。以 wp_list_comments_args 的值为准去同步后台。
  • 案例:一个测评站在文章爆量后发现评论区把首屏拖到 5 秒。按本文方案开启分页(每页 20)+ 头像缓存后降到 1.2 秒。上线当晚有用户反馈「刚发的评论不见了」,排查发现清理钩子只删了 _transient_mfcm_*,漏掉 _transient_timeout_*,导致过期时间残留、后续读到了旧数据。补齐第二条删除语句后恢复正常。
  • 关闭头像显示来提速:转化率会受影响,评论区的人际感下降明显,不建议。头像问题应当按 Gravatar 头像加载慢的解决 单独处理。

总结

评论区优化的判断标准很简单:单篇文章评论数超过 100 就应当开启分页,超过 300 就应当同时上头像缓存和列表片段缓存。操作顺序固定为「后台分页 → 参数统一 → 缓存 → 定向清理」,不要跳过第一步直接上缓存,分页才是把成本从线性变成常数的关键。上线后务必用隐身窗口模拟「提交新评论 → 立即刷新」验证缓存清理是否生效,这一步能挡掉八成的线上投诉。若站点使用了整页缓存插件,评论区通常需要按 WordPress 页面缓存排除规则 单独排除或走 AJAX 加载。

U
UFO_PF作者

专注 WordPress 生态的实战作者,内容以「可复制、可验证」为第一原则。

留下你的想法

邮箱地址不会被公开。带 * 的为必填项。