WordPress数据库查询优化高级教程

WordPress数据库查询优化高级教程插图

总所周知,WordPress是个动态查询和数据库密切相关的系统,在网站运行过程中,各类查询将决定网站的快慢。如何进行WordPress数据库查询优化呢?在这篇文章中,我们将解释一些我们为加快查询速度所做的事情,以及一些需要避免的事情。在进入这个话题之前,我们必须首先了解WordPress的查询和循环是如何工作的。

之前搬主题有介绍如何优化WordPress网站的函数,如下

一、循环系统的快速介绍

每当用户请求一个页面或文章时,WordPress就会用请求的slug点击数据库进行查询,以获得页面数据,比如标题和内容。然后主题使用 “The Loop“来显示这些数据,看起来像这样。

while( have_posts() ) {
    if( have_posts() ) {
        the_post();
        the_content();
    }
}

把这个分解一下。

  • have_posts() – 告诉The Loop是否还有帖子需要循环(得到一个布尔值为真或假)。
  • the_post() – 使用setup_postdata()来设置我们容易使用的函数,如the_content()the_title()。如果有任何额外的帖子需要循环,也会增加我们的帖子数组。

循环是非常简单和直接的。现在让我们来看看开发者可以通过哪些不同的方式来为The Loop获取帖子。

二、数据库询问查询

从技术上讲,有3种通用的方法供开发人员获取WordPress可以使用的帖子。

  • 跳到query_posts()
  • 跳到get_posts()
  • 跳转到WP_Query()

还有更多的函数可以返回帖子,但不是通用的,因为返回的帖子被改变了,或者你能做的事情有限。一个例子是get_pages(),它只对分层的帖子类型起作用。使用 “technically”这个词是因为并非上面列出的所有函数都是一样的;有一个函数在其他函数中脱颖而出。哪个查询是最好的?

1、函数:query_posts()

其实这句话已经以一千种不同的方式说了至少一百万次,我们可能(希望)不需要重申,但我们要重申。没有任何一种情况下,这个功能是适合使用的。甚至《WordPress的法典》也指出。

注意:这个函数不是用来给插件或主题使用的。有更好、更有效的方法来改变主查询。

这个函数的意思是,它将用新的东西覆盖当前的请求。例如,如果你有一个 “推荐”页面,并想用你的自定义帖子类型 “推荐”的帖子列表来替换这个页面,你可以这样使用这个函数。

query_posts( array(
	'post_type'		=> 'testimonials',
	'posts_per_page'	=> 200,
) );

if( have_posts() ) {
	while( have_posts() ) { the_post();
		the_content();
	}
	
	wp_reset_query();
}

这个函数的核心问题是,它覆盖了WordPress的原始查询,并改变了一些重要的globals。这意味着,我们将看到的不是显示推荐页面的内容,而是一个推荐列表,这听起来正是我们想要做的,但有几个问题。看,每当WordPress加载的时候,它都会使用页面lug或post ID查询数据库,以获取必要的数据,并把它转储到一个全局的$wp_query对象中,这样我们就可以在The Loop中访问和处理它(如上所示)。当WordPress在处理和运行这个query_posts()函数时,它将需要执行一个额外的数据库调用,并使用这些数据来覆盖全局$wp_query和全局$post对象。这就造成了不必要的开销。最后,我们需要运行wp_reset_query()来重置我们的全局变量,使其回到原始值。

但当我们可以使用一些更简单、更有效的方法时,为什么要经历所有这些呢?

动作钩子: pre_get_posts

很美妙和有用的钩子,这个钩子减轻了使用query_posts()的需要,因为我们可以在主查询从数据库被请求之前拦截它,并在这里改变它。如果开发者需要改变存档页面上显示的帖子数量,这就特别有用(这样就不会弄乱每页的默认帖子)。让我们把上面使用query_posts()的例子,用pre_get_posts把它变成一个更有效的查询。

function theme_pgp( $query ) {
	if( is_page( 'testimonials' ) ) {
		$query->set( 'post_type', 'testimonials' );
		$query->set( 'posts_per_page', 200 );
	}
}
add_action( 'pre_get_posts', 'theme_pgp' );

上述做法将实现与query_posts()例子完全相同的事情,而不需要进行额外的数据库请求。不幸的是,你仍然会失去我们可能想保留的 “见证”页面内容。让我们来看看一些二级查询!

2、函数: get_posts()

这个函数不像query_posts那样低效,它将返回一个标准的PHP数组的Post对象。这个函数生成的是一个所谓的 “二级查询”,因为它不会覆盖任何全局变量。考虑到这一点,在默认情况下,如果不使用前面 “循环介绍 “中提到的setup_postdata()函数,你也无法访问所有那些方便的函数,如the_content()和the_title()。如果你使用setup_postdata()函数,还需要运行wp_reset_postdata()来规范全局$post对象。下面的例子是get_posts()的最基本用法,你可以把它放在正常内容循环的上方或下方:

if( have_posts() ) {
	while( have_posts() ) {
		the_post();
		the_content(); // Normal Testimonial Page Content
	}
}

$testimonials = get_posts( array(
	'post_type'		=> 'testimonials',
	'posts_per_page'	=> 200
) );

if( ! empty( $testimonials ) ) {
	foreach( $testimonials as $post ) { setup_postdata( $post );
		the_content(); // Each testimonials post type's content
	}
	
	wp_reset_postdata();
}

以上是相当伟大的简单性,但它仍然不是最好的。只是为更强大的东西做了一个包装!

3、最全的查询…WP_Query

WordPress数据库查询优化高级教程插图1
WordPress查询图

上述所有的查询功能都会创建一个WP_Query类的实例。还不如跳过中间环节,直接跳到使用WP_Query。这个类最好的部分是,与get_posts()不同,你可以使用正常的WordPress循环程序来编写你的二级查询,这将使你能够访问那些多汁的the_content()函数。由于我们能够使用这些函数,这意味着global $post对象将被覆盖,我们将需要运行wp_reset_postdata()来规范化。让我们使用get_posts()的例子并将其转换为WP_Query。

$query = new WP_Query( array(
	'post_type' 		=> 'testimonials',
	'posts_per_page'	=> 200,
) );

if( have_posts() ) {
	while( have_posts() ) {
		the_post();
		the_content(); 	// Normal Page Content
	}
}

if( $query->have_posts() ) {
	while( $query->have_posts() {
		$query->the_post();
		the_content();	// Testimonial Secondary Query Content
	}
	
	wp_reset_postdata();
}

干净而简单! 除此之外,WP_Query对象让我们可以访问一些伟大的属性和方法,我们可以在循环中使用,比如:

  • 属性:$query->current_post是一个计数器,让我们知道迄今为止我们已经循环了多少个帖子(当前帖子的索引)。
  • 属性:$query->post_count让我们知道有多少帖子在我们的循环中。对模数的计算很有帮助。
  • 方法:$query->rewind_posts()将把帖子数组设置为0,这在我们需要多次循环查询时非常有用。

你可以用很多不同的组合来拉动帖子。

二、关于数据库查询优化!

上面我们已经说服您永远使用WP_Query(希望如此……),让我们回顾一下我们可以使用的一些属性,使我们的查询执行得更快、更有效。这里是一个场景:

你的客户想在他们的网站上添加一个推荐人的名单。但他们碰巧讨厌分页,对分页非常抵制,所以他们想在最初的页面加载时显示所有的推荐。

很好,听起来很简单,对吗?让我们检查一下The Codex,看看他们的分页部分是怎么说的。

posts_per_page (int) – 每页要显示的帖子数量。使用’post_per_page’=>-1来显示所有帖子…

1、参数:post_per_page

好吧,你已经实施了上述措施,客户很高兴,所以你也很高兴。也就是说,直到几个月后,你的客户的业务已经爆炸性地增长,并且一直在增加客户的推荐。也许他们在网站上放了一个开放的表格,让用户提交推荐信。不管是什么情况,现在有1,000多个推荐信试图显示在那个单一页面上。他们的服务器很可能无法处理这种负载,PHP可能无法处理这么多的帖子。服务器瘫痪了,你看到500内部服务器错误,这10次中有1次不是一个好兆头。

如果你现在还没有猜到,设置post_per_page => -1不是很好的可扩展性,随着业务的发展,可能会导致以后的问题。你可以升级你的服务器,但最终这将是不够的,所以一个更好的解决方案是设置一个静态的、但大的上限值。好的上限范围是100-500:post_per_page => 200,这可能不会给你的客户带来他们想要的东西,但我们觉得这比向用户显示内部服务器错误屏幕要好。可能两全其美的办法是使用一个懒惰的加载插件,在需要时动态地拉入帖子,如Ajax Load More。这也被称为 “无限滚动”。

2、参数: no_found_rows

我们可以把这个参数设置为true:no_found_rows => true来停止任何形式的分页计算。如果我们不设置这个参数,无论posts_per_page的值是多少,MySQL都会尝试获取与查询相匹配的所有帖子。使用同样的方案,我们的客户还希望在每页的侧边栏显示10个推荐。如果我们不把这个参数设置为 “true”,那么WordPress就会在所有1000多个页面中寻找,以计算将有多少个页面,尽管在这个侧边栏查询中没有分页。在这种情况下,这种计算是不必要的开销,可以规避的。

如何设置?可以参考搬主题之前的文章

3、参数: fields

一般WordPress只有2个选项可以将字段参数设置为:

  • 'fields' => 'ids'
  • 'fields' => 'id=>parent'

这告诉我们的查询只返回帖子的ID,而没有其他内容。乍一看,这似乎没有什么用,所以我们将给你一些免费的代码来解释。下面是一个例子,测试一个给定的页面下面是否有子进程。因为我们不需要任何其他的值,所以我们只返回ID,并确保至少有一个帖子,这提高了我们的查询速度:

function has_children( $post_id, $post_type = 'page' ) {
	$children = new WP_Query( array(
		'post_parent'		=> $post_id,
		'post_type'		=> $post_type,
		'posts_per_page'	=> 1,
		'post_status'		=> array( 'publish' ),
		'fields'		=> 'ids',
	) );
	
	return ( $children->have_posts() );
}

这里有另一个例子。假设你需要每个月运行一个WordPress cronjob来删除90天以上的推荐。wp_delete_post()函数只需要一个帖子的ID就可以运行,所以我们需要查询的只是帖子的ID,这使得这个参数非常完美。

4、参数: update_post_term_cache 和 update_post_meta_cache

在我们进入这两个参数之前,我们需要知道WordPress是如何处理它的一些缓存的。这里有一个简单的介绍。

每当WordPress运行一个几乎任何种类的查询时,它都会缓存它可能需要的所有数据,这通常被称为 “对象缓存”。最明显的是postmeta、术语和术语关系。这很方便,因为如果我们真的需要像postmeta这样的东西,WordPress会进入缓存,而不是运行一个额外的数据库查询。同样,如果你需要的话,这很方便,但如果你不打算使用任何postmeta或检查术语,那么WordPress就没有必要抓取任何这些信息,这只是不必要的开销。幸运的是,WP_Query为您提供了上述的参数。

update_post_term_cache 参数

是否缓存 post term 的内容,默认也是 true。可以设置为false。

update_post_term_cache 开启之后,在列表页使用 get_the_terms 函数的时候,不需要导数据里面去请求每个 post 的各种 taxonomy 的 term 的信息,它会把整个列表所有文章的所有 taxonomy 的 term 一起全部请求出来

update_post_meta_cache 参数

是否缓存 post meta 的内容,默认是 true。可以设置为false。

update_post_meta_cache 开启之后,在列表页使用 get_post_meta 函数的时候,不需要导数据里面去请求每个 post_id 的 post meta 的信息,它会把整个列表所有文章的 post meta 一起全部请求出来

现在你知道WordPress抓取了所有的东西,这些参数可能更有意义了。如果我们不需要或不打算使用postmeta,我们可以告诉我们的查询甚至不看postmeta表:’update_post_meta_cache’ => false,术语也是如此。’update_post_term_cache’ => false。你可以通过使用’cache_results’ => false把这两个参数变成false,但是如果你打算使用meta或term函数,哪怕是一次,这些参数都不值得关闭。WordPress会产生另一个数据库调用来获取元或术语关系,并且无论如何都会缓存这些数据。当然,不要用pre_get_posts来关闭这些。如果你把你的项目交给另一个开发者,而在pre_get_posts中关闭了缓存,那么新的开发者可能会意外地进行额外的数据库查询来获取postmeta,而不知道缓存已被禁用。

三、额外的查询缓存

如果你需要缓存实际的MySQL数据库结果,WordPress也有几个不错的功能:

  • 参数: wp_cache_set()
  • 参数: wp_cache_get()

上述函数被认为是 “非持久性缓存”,这意味着它们将在页面刷新时消失(不像瞬时函数是持久的)。如果我们需要通过页面多次进行相同的MySQL查询,这就很有用。例如,如果我们需要创建一个postmeta值的过滤器,我们可以进行一次数据库调用,获得所有不同的值:

$results = $wpdb->get_col( $wpdb->prepare( "
	SELECT DISTINCT pm.meta_value FROM {$wpdb->postmeta} pm
	LEFT JOIN {$wpdb->posts} p ON p.ID = pm.post_id
	WHERE pm.meta_key = '%s' 
	AND p.post_status = '%s' 
	AND p.post_type = '%s'
", $key, $status, $type ) );

如果我们需要在侧边栏和主要内容之前都显示这个内容,我们就不会想两次运行这个费力的查询。相反,我们可以通过wp_cache_set( ‘unique_meta’, $results )在第一次调用时设置缓存,然后通过wp_cache_get( ‘unique_meta’)来获得我们的结果,而不需要额外调用数据库。有趣、快速、简单。

希望上面的内容已经阐明了WordPress的工作原理和一些加快查询速度的方法。你可能会说,缓存插件会照顾到以上所有的问题,你不会错。以上应该假设你的客户可能没有缓存插件,也许以上的查询会被内置到WordPress仓库的一个插件或主题中。

另外还有些其他查询的优化

cache_results

是否缓存查询的文章信息。

默认情况分两种,使用外部对象缓存(比如使用 Memcached)就是 false,没有使用则是 true。

做了几次测试,true 和 false 没什么区别,感觉有点重复,所以这个建议设置为 false

update_post_caches 函数

WordPress 会使用 _prime_post_caches 这个函数进行批量的 ids 的pote_term 和 post_meta 的请求:

_prime_post_caches( $ids, $q['update_post_term_cache'], $q['update_post_meta_cache'] );

它的源代码很好的解释了它的工作原理是:

function _prime_post_caches( $ids, $update_term_cache = true, $update_meta_cache = true ) {
	global $wpdb;

	$non_cached_ids = _get_non_cached_ids( $ids, 'posts' );
	if ( !empty( $non_cached_ids ) ) {
		$fresh_posts = $wpdb->get_results( sprintf( "SELECT $wpdb->posts.* FROM $wpdb->posts WHERE ID IN (%s)", join( ",", $non_cached_ids ) ) );

		update_post_caches( $fresh_posts, 'any', $update_term_cache, $update_meta_cache );
	}
}

首先使用 _get_non_cached_ids 函数获取未缓存的 post_ids(如果开启了 Memcached,这里就可以自动实现返回为空,就会大大减少 SQL 请求),然后使用一条 IN 查询获取这些 post_ids 的内容,最后再使用 update_post_caches 将新获取的 posts 缓存起来,然后并且一次性求获取所有相关的 post_term 和 post_meta。

lazy_load_term_meta 参数

是否延迟加载 term meta 的内容,如果没有设置,默认根据 update_post_term_cache 的值而定。

如果为 true 的话,WP_Query 会把列表页所有的 term_ids 临时存储下来,在当前页第一次使用 get_term_meta 函数的时候,把 term_ids 的所有 term_meta 一次全部请求出来。

如果设置为 false 的话,每个 get_term_meta 的函数,都会产生一条 SQL 请求。

当然你也可以自己收集所有相关的 term_ids,然后使用 update_termmeta_cache($term_ids) 来一次获取所有 term_meta 的值。

 收藏 (0) 更新不易,打赏吧

您可以选择一种方式赞助本站

支付宝扫一扫赞助

微信钱包扫描赞助

除特别注明外,本站所有文章均基于CC-BY-NC-SA 4.0原创,转载请注明出处。
文章名称:《WordPress数据库查询优化高级教程》
文章链接:https://www.banzhuti.com/wordpress-query-optimization.html
分享到: 生成海报
版权免责声明

① 本站提供的资源(插件或主题)均为网上搜集,如有涉及或侵害到您的版权请立即通知我们。
② 本站所有下载文件,仅用作学习研究使用,请下载后24小时内删除,支持正版,勿用作商业用途。
③ 因代码可变性,不保证兼容所有浏览器、不保证兼容所有版本的WP、不保证兼容您安装的其他插件。
④ 本站保证所提供资源(插件或主题)的完整性,但不含授权许可、帮助文档、XML文件、PSD、后续升级等。
⑤ 由本站提供的资源对您的网站或计算机造成严重后果的本站概不负责。
⑥ 使用该资源(插件或主题)需要用户有一定代码基础知识!另本站提供汉化使用安装教程,仅供参考。
⑦ 有时可能会遇到部分字段无法汉化,同时请保留作者汉化宣传信息,谢谢!
⑧ 本站资源售价只是赞助和汉化辛苦费,收取费用仅维持本站的日常运营所需。
⑨ 如果喜欢本站资源,欢迎捐助本站开通会员享受优惠折扣,谢谢支持!
⑩ 如果网盘地址失效,请在相应资源页面下留言,我们会尽快修复下载地址。

热门文章

评论 抢沙发

评论前必须登录!

立即登录   注册

WordPress主题 插件 建站 汉化

定制服务联系我们
切换注册

登录

点击按钮进行验证

忘记密码 ?

您也可以使用第三方帐号快捷登录

切换登录

注册

我们将发送一封验证邮件至你的邮箱, 请正确填写以完成账号注册和激活