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

总所周知,WordPress是个动态查询和数据库密切相关的系统,在网站运行过程中,各类查询将决定网站的快慢。如…

IMG_0

总所周知,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

IMG_0
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多个页面中寻找,以计算将有多少个页面,尽管在这个侧边栏查询中没有分页。在这种情况下,这种计算是不必要的开销,可以规避的。

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

解决SQL_CALC_FOUND_ROWS数据库慢查询问题的解决方案

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 的值。

类别:WordPress 进阶教程

本文收集自互联网,转载请注明来源。
如有侵权,请联系 wper_net@163.com 删除。

评论 (0)COMMENT

登录 账号发表你的看法,还没有账号?立即免费 注册