跳到正文
原文
Yoast 官方博客· Leonidas Milosis·· 2026-06-12精选编辑评分68

Yoast SEO 27.8 发布大型网站性能优化

New: Yoast releases performance optimizations for larger websites

编辑导读

Yoast SEO 27.8 通过优化数据库查询、减少往返次数和避免编辑器重复渲染,改善大型网站的加载与后台性能。测试中,包含约 200 万用户的站点生成 root sitemap 的单次查询时间从 300 多秒降至 25 毫秒;对 1000 篇文章执行部分操作时,数据库往返次数由 1000 次减至 40 次。

推荐理由

文章拆解了 Yoast SEO 27.8 的查询优化、缓存与批量读取策略,并提供大型用户量和文章量场景的测试细节。

在我们最新的 27.8 版本中,我们引入了性能优化,应该能缩短插件各项功能的加载时间,在拥有大量帖子和用户的大型网站上尤其明显。

注意:本文包含技术内容和实现细节。

提供服务器额外开销极低且加载速度快捷的精心优化软件,始终是 Yoast 开发者所做的一切工作的首要任务。然而,Yoast SEO 已安装在数百万个网站上,因此我们需要针对其进行良好优化的环境差异很大。这意味着我们应该持续寻找机会来优化插件的性能。过去我们一直坚持这样做,例如改进数据库系统时就是如此。

27.8 版本正是其中一次专项审查的成果。我们特意挑选了在大规模运行时最有优化空间的功能,并对其进行了重构,使其更精简、更快速。从修改查询以加快拥有大量用户的网站上的页面速度,减轻拥有大量帖子的网站在管理后台中的繁重操作,到减少多项功能对数据库的往返查询,以及普遍应用性能最佳实践,这次发布旨在改善 Yoast SEO 插件的用户和开发者体验。

我们还希望在这里提供本次版本改进的技术摘要,重点介绍其中的细节,因为提高人们对性能最佳实践的认识总是有益的(更不用说谈论代码总是很有趣了)。

显著缩短拥有大量用户的网站上根 sitemap 的加载时间

作为背景,Yoast SEO 在输出根 sitemap 时,需要计算作者 sitemap 的 Last Modified 值,为此会使用所有符合条件、可被纳入作者 sitemap 的用户的 usermeta。

传统上,符合条件的用户是通过检查用户权限来确定的。具体做法是在所使用的 get_users() 调用中添加 ‘capability’ => [ ‘edit_posts’ ] 参数。结果会触发一条非常沉重、包含多重联接且未使用数据库索引的查询。

具体而言,最终生成的查询会添加类似下面的子句:

AND ((((mt1.meta_key = 'wp_capabilities'
        AND mt1.meta_value LIKE '%\"edit\\_posts\"%')
       OR (mt1.meta_key = 'wp_capabilities'
           AND mt1.meta_value LIKE '%\"administrator\"%')
       OR (mt1.meta_key = 'wp_capabilities'
           AND mt1.meta_value LIKE '%\"editor\"%')
       OR (mt1.meta_key = 'wp_capabilities'
           AND mt1.meta_value LIKE '%\"author\"%')
       OR (mt1.meta_key = 'wp_capabilities'
           AND mt1.meta_value LIKE '%\"contributor\"%')
       OR (mt1.meta_key = 'wp_capabilities'
           AND mt1.meta_value LIKE '%\"wpseo\\_manager\"%')
       OR (mt1.meta_key = 'wp_capabilities'
           AND mt1.meta_value LIKE '%\"wpseo\\_editor\"%'))))

由于 LIKE ‘%…%’ 无法使用任何 B-tree 索引,MySQL 必须完整读取每个匹配的 wp_capabilities 行,并对每行序列化后的 PHP meta_value 执行七次子字符串扫描。

我们将这项计算从使用权限检查改为查找拥有已发布帖子的用户(通过使用 ‘ has_published_posts ‘ => true 参数),立即使生成的查询改为使用索引,并且在拥有大量用户的网站上执行效果大幅提升。

事实上,在我们的一个测试中,在一个拥有约 200 万用户的网站上,完成每次查询所需的时间(也就是根 sitemap 大致所需的渲染时间),从超过 300 秒降至仅仅 25 毫秒!这意味着,在类似网站上,此项更改有望大幅改善根 sitemap 的加载时间。

最后,考虑到 ‘ has_published_posts ‘ => true 参数在 sitemap 生成的后续阶段已经被使用,因此这项更改本身对功能的实际表现应该几乎没有负面影响,甚至完全没有负面影响。

缩短拥有大量用户的网站上作者 sitemap 的加载时间

要让 Yoast SEO 渲染作者站点地图,它需要计算符合条件的用户。在用户众多的网站上,这可能是一项非常繁重的操作。除上述优化外,我们注意到,在 Yoast SEO 计算符合条件的用户时,它还会添加一个元查询,用于检查每个用户的 user_level 是否大于 0。

事实证明,这是旧时代的遗留产物,因为 WP 核心从 3.0 版本起就已弃用 user_level 框架。虽然这并未导致我们的站点地图功能出现问题,但它却不必要地在生成的查询中添加了 INNER JOIN,而且在用户表和 usermeta 表非常大的网站上,这会降低性能。因此,我们移除了这个不必要的 JOIN:

INNER JOIN wp_usermeta AS mt1 ON wp_users.ID = mt1.user_id 

... 

AND ( mt1.meta_key = 'wp_user_level' AND mt1.meta_value != '0' )

由于 user_level 框架很久以前就已被弃用,我们经过审慎考虑,决定放弃对它的支持,尤其是这样能让我们的功能运行得更顺畅。事实上,我们认为可以放心发布这项优化,并预计几乎不会造成中断,恰恰是因为该弃用决定已经非常久远。

防止管理页面中不必要的高成本数据库查询

过去,为了及时通知管理员采取必要操作,使站点数据在我们的内部存储中得到最佳索引,Yoast SEO 会在管理员浏览后端期间每天运行一次数据库查询。对于大型网站,该数据库查询可能耗时数秒,从而定期拖慢管理页面的渲染速度。

具体来说,就是以下函数:

Limited_Indexing_Action_Interface::get_limited_unindexed_count()

它可能会运行如下复杂查询;这些查询过去会定期在管理页面上运行,从而拖慢大型网站上的页面渲染速度。

SELECT Count(P.id) 
FROM   wp_posts AS P 
WHERE  P.post_type IN ( 'post', 'page' ) 
       AND P.post_status NOT IN ( 'auto-draft' ) 
       AND P.id NOT IN (SELECT I.object_id 
                        FROM   wp_yoast_indexable AS I 
                        WHERE  I.object_type = 'post' 
                               AND I.version = 2)

我们重新组织了负责通知管理员待处理操作的代码逻辑,使这些高成本查询现在仅在首次检测到需要创建此类通知时运行一次。

这样一来,我们实际上会缓存

Limited_Indexing_Action_Interface::get_limited_unindexed_count()

的结果,并依赖改动之前就已存在、但未得到妥善利用的缓存失效机制。因此,一个可能非常耗时的高成本数据库查询,从每天触发一次(在用户并发量很大、非常繁忙的网站上,每 15 分钟触发一次)变成了在大多数网站上仅触发一次。

优化管理页面中的高成本数据库查询

与上述防止查询的改动相关,我们不仅避免了每个网站多次运行上述高成本数据库查询,还优化了查询本身。由此带来的额外好处是,在拥有大量文章的网站上,SEO 优化工具的运行速度大幅提升。

具体来说,我们从:

AND P.ID NOT IN ( 
    SELECT I.object_id FROM wp_yoast_indexable AS I 
    WHERE I.object_type = 'post' 
)

改为:

AND NOT EXISTS ( 
    SELECT 1 FROM wp_yoast_indexable AS I 
    WHERE I.object_id = P.ID 
      AND I.object_type = 'post' 
)

由于 NOT IN (subquery) 会构建完整的 object_ids 列表,而第二个查询一旦有一行匹配便会短路,因此在拥有数千篇文章的网站上,该查询的运行速度要快得多。

减少对数据库的往返查询

根据经验法则,对数据库的往返查询属于开销较高的操作,应尽可能减少。我们的审查发现了一些情况:我们原本通过连续的 SELECT 查询分别获取多篇文章的数据,而实际上可以使用单次批量 SELECT 查询一次性获取所有文章的数据。

例如,一段原本如下所示的代码:

$indexables = []; 

foreach ( $post_ids as $post_id ) { 
	$indexables[] = $this->repository->find_by_id_and_type( (int) $post_id, 'post' ); 
}

被重构为如下所示的代码:

$ indexables = $this->repository->find_by_multiple_ids_and_type( 
	\array_map( 'intval', $post_ids ), 
	'post', 
);

这意味着,对于包含 1000 篇文章的一批内容,我们不再执行 1000 次最多返回一行的 SELECT 查询,而是改为执行一次最多返回 1000 行的 SELECT 查询 Naturally,我们确保每次请求的文章数量不超过某个阈值,以避免触及 MySQL 使用限制。

因此,以拥有 1000 篇文章的网站为例,在某些操作中,例如部分 SEO 优化或 schema 聚合功能输出的部分操作,可以节省 960 次数据库往返。

通过防止不必要的重新渲染来提升文章编辑器性能

WordPress 编辑器会在侧边栏面板从 store 中提取的数据看似发生变化时重新渲染 Yoast 的侧边栏面板。遗憾的是,“看似发生变化”是通过引用相等(JavaScript 的 ===)而非比较值来判定的。返回 { items: [‘foo’] } 的选择器在人类看来完全相同,但如果每次都是一个新的对象字面量,React 就会将其视为新对象并重新渲染面板。而如果再乘以一个繁忙的编辑器——它在每次按键时都会分发状态更新——结果就是面板毫无缘由地持续重新渲染。

在 27.8 版本中,我们发现数据实际上并未发生变化却触发文章编辑器不必要重新渲染的多个情况,并对其进行了修复,使我们的编辑器集成更加稳健、性能更佳。

文章《全新:Yoast 为大型网站发布性能优化》最初发布于 Yoast。

来源:Yoast 官方博客 · https://yoast.com/yoast-seo-june-12-2026/

部分链接为推广/关联链接。