慢速 SQL 查询可能会严重影响 WordPress 网站的性能。 有时候,慢查询是由于编写不当的 SQL 语句导致的,这些语句本不应该这样编写。 有时候,慢查询在某个时间点实际上是快速查询,但随着网站的不断发展,查询变得越来越慢,无法跟上不断扩大的数据库。
无论您的 SQL 如何变慢,让我们看看在 WordPress 中查找和修复问题查询的几种方法。
查找慢查询
查找慢查询的来源涉及两个步骤
- 确定哪些查询实际上是慢查询。
- 找到生成和执行这些查询的代码。
让我们看看两个插件和一个 SaaS,它们可以帮助我们找到慢查询。
Query Monitor
Query Monitor 是一个 插件,它提供了关于当前页面的大量信息。 除了关于 WordPress 内部工作原理的大量信息外,它还提供了以下内容的详细细分:
- 此请求中发生了多少个查询
- 页面上哪些查询花费的时间最长
- 哪些函数在 SQL 查询上花费了最多时间
- 这些查询是否来自插件、主题或 WordPress 核心

Query Monitor 甚至会用醒目的红色文本识别慢查询,这使得挑选出有问题的 SQL 变得超级容易。

Debug Bar
另一个用于查找非常慢的 SQL 的优秀工具是久经考验的 Debug Bar 插件。 当您加载页面时,Debug Bar 会提供有关 WordPress 内部工作原理的信息,例如:
- WP_Query 参数
- 请求信息(包括重写规则匹配)
- 当前页面生成的 SQL 查询
要在 Debug Bar 中启用 #3(SQL 跟踪),请确保在您的网站上某个地方启用 SAVEQUERIES
,可能在 wp-config.php
中,如下所示:
if ( ! defined( 'SAVEQUERIES' ) ) {
define( 'SAVEQUERIES', true );
}
警告:SAVEQUERIES
会影响您网站的性能,并且可能不应在生产服务器上使用。 请改在开发机器上使用它。
使用 Debug Bar 查找慢速 SQL 并不容易。 例如,它不会为您提供可排序的表格或突出显示慢查询。 Debug Bar 提供的是一个函数跟踪,它可以准确地告诉您在哪里可以找到查询的来源。

这是一个加载的文件列表以及导致查询执行的函数。 大多数情况下,您感兴趣的是列表中的最后一个条目; 这是慢查询执行的位置,也是您应该开始搜索的位置。 拥有导致此查询的每个函数的上下文的好处是,它可以阐明为什么首先执行该 SQL。
NewRelic
NewRelic 是一项服务,用于测量和监控 Web 应用程序(包括 WordPress)的性能。 该服务提供了关于网站性能的海量信息。 您可以从详细的代码执行到 SQL 查询的行级细分中轻松迷失在 NewRelic 提供的数据中。

NewRelic 和我们之前提到的插件之间有两个主要区别:
- NewRelic 提供了更多关于 PHP 性能的详细信息,精确到每个函数花费的毫秒数。
- NewRelic 在后台跟踪对您网站的每个请求,因此您可以稍后参考它来查找慢速 SQL。 插件仅为您提供当前页面。
值得注意的是,NewRelic 有一个免费的计划层级,它提供有关网站性能的一般信息,但您需要 升级到付费计划 才能获得监控单个请求和查找慢查询的全部功能。
使用 EXPLAIN 理解慢查询
到目前为止,我们已经介绍了查找慢查询的工具。 现在让我们弄清楚为什么这些查询会拖慢速度。
MySQL 的 EXPLAIN
关键字可以帮助解释正在发生的事情。 在查询的开头添加 EXPLAIN
将显示 MySQL 如何执行查询。 对于复杂的查询,EXPLAIN
可以帮助识别 SQL 中的慢点,例如慢速子查询或低效操作。
例如,如果您有一个如下所示的查询:
SELECT slow_column FROM slow_table
您可以通过简单地运行以下命令来解释该查询:
EXPLAIN SELECT slow_column FROM slow_table
以下是 EXPLAIN 在 phpMyAdmin 中的输出:

老实说,我并不完全理解 MySQL 的所有内部工作原理,但对查询运行 EXPLAIN
仍然可以洞悉 MySQL 如何执行我的 SQL。 查询是否使用了索引? 它是否扫描了整个表? 即使对于简单的查询,EXPLAIN 也提供了一些信息来帮助理解正在发生的事情。
您可以从 MySQL 命令行或您喜欢的 MySQL 工具运行 EXPLAIN。
修复慢查询
现在我们知道我们的查询很慢,并且 EXPLAIN
告诉我们为什么它很慢,让我们看看一些修复这些缓慢问题的选项。
选项 1:更改查询
在 CSS-Tricks 上,我们有一个查询导致“编辑文章”屏幕速度非常慢。 该查询是自定义字段元框的一部分。 以下是 SQL:
SELECT meta_key
FROM wp_postmeta
GROUP BY meta_key
HAVING meta_key NOT LIKE '\\_%'
ORDER BY meta_key
LIMIT 100
这段特定的 SQL 正在从 wp_postmeta
表中获取不以下划线 (_
) 开头的 meta_keys
列表。 GROUP BY
语句意味着每个结果都是唯一的。
运行此查询 5 次,以下是花费的时间:
1.7146 秒
1.7912 秒
1.8077 秒
1.7708 秒
1.8456 秒
我们能否编写不同的查询来获得相同的结果? 我们需要选择唯一的 meta_keys
。 唯一是distinct 的同义词,它碰巧也是一个 SQL 语句!
使用 DISTINCT
语句,我们可以执行以下操作:
SELECT DISTINCT meta_key
FROM wp_postmeta
WHERE meta_key NOT LIKE '\\_%'
ORDER BY meta_key
运行我们重写的查询几次,得到以下结果:
0.3764 秒
0.2607 秒
0.2661 秒
0.2751 秒
0.2986 秒
这并不是一个科学的比较,但确实揭示了显著的改进!
选项 2:添加索引
当您对标准 MySQL 表运行 SQL 查询时,MySQL 必须扫描整个表以确定哪些行与此特定查询相关。 当您的表变得非常大时,这种扫描开始花费很长时间。
这就是 MySQL 索引的用武之地。 索引获取表中的数据并以一种使数据更容易定位的方式对其进行组织。 通过以特定方式组织数据,索引有助于减少 MySQL 对每个查询执行的扫描量。
索引可以添加到单个列或多个列。 语法如下所示:
CREATE INDEX wp_postmeta_csstricks ON wp_postmeta (meta_key)
在 meta_key
上添加索引后,原始 SQL 查询时间如下所示:
0.0042 秒
0.0024 秒
0.0031 秒
0.0026 秒
0.0020 秒
这非常快!
INSERT
创建一行或 UPDATE
用于索引表时,索引都会重新计算,这可能是一项代价高昂的操作。 索引使从表中读取数据更快,但向表中写入数据则更慢。 合理放置的索引可以使您的查询飞速运行,但在监控索引对数据库的整体影响之前,不要疯狂地添加索引。
选项 3:缓存查询结果
我们知道有一个慢查询。与其更改查询,不如我们只存储查询的结果?这样,我们就可以限制查询执行的频率,并且大多数情况下都可以获得“免费通行证”。
要缓存查询,我们可以使用 WordPress 的 瞬态 API。瞬态用于存储耗时操作的结果,例如
- 对外部网站的请求(例如,获取最近的 Facebook 帖子)
- 缓慢的处理片段(例如,使用正则表达式搜索大型字符串)
- 缓慢的数据库查询!
使用瞬态存储查询结果看起来像这样
if ( false === ( $results = get_transient( 'transient_key_name' ) ) ) {
$results = ...; // Do the slow query to get the results here
// 60 * 60 is the expiration in seconds - in this case, 3600 seconds (1 hour)
set_transient( 'transient_key_name', $results, 60 * 60 );
}
像这样在瞬态中存储查询结果意味着查询大约每小时才会执行一次。这导致了瞬态重大警告:在使用瞬态存储频繁变化的数据时要小心。
如果您的查询结果不会经常更改,则使用瞬态是一种巧妙的方法,可以避免频繁访问数据库。
选择方法
我们概述了三个选项,并且可能还有 17 种其他方法可以解决此慢查询问题。我们采取哪种方法?
在处理不是我自己的代码时,我更喜欢遵循程序员的格言:“做最简单的事情,使其能够正常工作。”
选项 1(重写查询)产生了极好的结果,但如果重写的查询并不总是具有相同的结果怎么办?我们可能会无意中用一个略有偏差的查询破坏我们的代码。
选项 2(添加索引)并非总是可行,具体取决于查询使用的表和列。对于 WordPress 核心表,您需要担心索引的副作用
- 核心更新例程是否期望额外的索引?
- 添加索引是否会减慢其他查询的速度,例如
INSERT
和UPDATE
?
选项 3(通过瞬态缓存结果)的影响最小——我们没有更改原始查询,也不需要修改数据库结构。
大多数情况下我使用选项 3。在您的特定情况下,您可能会根据要修复的查询或遇到 SQL 问题的特定站点选择其他选项。大多数性能问题没有一刀切的答案,因此您可以自由地不同意我的选择——或者,一次尝试所有三个选项!
敬请期待
我们在这里概述了一个实际问题。CSS-Tricks 上的自定义字段框确实是导致某些非常慢的数据库查询的罪魁祸首。我们还概述了通往潜在解决方案的不同路径,但我们实际上并没有提供真正的代码解决方案。我们很快就会发布第二篇文章来介绍这一点,并希望为您提供在发现慢查询后修复自己的慢查询的工具。
很棒的文章!谢谢。
太棒了!感谢分享!
当瞬态存储在数据库中(而不是像 APC、Xcache 或 Redis 那样)时,已过期的条目仅在请求时才会被删除。如果您的键包含一定程度的唯一性(例如用户 ID 或其他内容)并且不经常被调用,那么数据库中可能会存在大量膨胀。
我还使用过数据库查询的文件级片段缓存来加快具有大量昂贵查询的网站的速度。您可以将 wp_cache_set() 和 wp_cache_get() 与您选择的持久缓存工具一起使用。类似于瞬态,但您可以将其保存在文件中或内存中,这应该更快。您可以使用用户 ID 自定义片段,如果它们特定于特定用户,例如收藏或未读帖子的列表。
我有一个网站,某些页面上的复杂查询需要 3-5 秒才能完成,并且将其减少到每页 0.4 – 0.6 秒。对于访问者来说,体验有了很大的提升!
这是我的秘密,我所有的查询都很慢
不错的文章,学到了一些东西。
使用视图和存储过程作为提高查询性能的方法怎么样?
真正的问题是 WordPress 无法访问 MySQL 慢查询日志,如果可以的话,它可以共享常见的慢查询,以建议重写 WordPress 核心。
精彩而深入的文章,提供了极好的指导。
请注意,MySQL 具有用于慢查询日志记录的内置功能
https://dev.mysqlserver.cn/doc/refman/5.1/en/slow-query-log.html
文章中提到的某些工具可能使用了此功能,但也可以在没有任何其他工具的情况下完成,只是说一下。
您可以在“save_post”钩子中包装一个 delete_transient() 函数,以防止在有新数据可用时缓存查询。
嗨,Andy,
感谢您的提示。使用 NewRelic 没有像它应该的那样帮助我们解决问题,并且处理我们网站 somproduct.ro 的支持人员或多或少是隐形的。12 天后,我们决定不再使用他们的服务。我们的体验非常糟糕,但我们肯定需要找到其他方法来防止加载时间过长并监控我们的网站。
Myles McNamara 提供了一个具体的查询问题,这对他的帮助很大
我发现了一种方法,它极大地提高了加载时间(约 14 秒降至约 2 秒或更少),这是由于 post_meta 表中的 meta_key 索引未被使用,此处有参考
https://core.trac.wordpress.org/ticket/33885
所有这些问题都通过简单地运行以下 MySQL 命令得到解决
来自 Automattic 的 Tom Nowell 发表评论