使用WP_Query構建高效的WordPress查詢

使用WP_Query構建高效的WordPress查詢

作為WordPress開發人員,我們經常需要從WordPress資料庫中檢索符合特定條件的文章、頁面和其他內容。通常,我們不需要構建SQL查詢(通常我們不應該),因為WP_Query類及其方法為我們提供了一種從資料庫中檢索資料的安全有效的方法。我們只需要宣告一個引數陣列,$query物件就會構建實際的SQL查詢。

在這篇文章中,我將假設您已經瞭解WP_Query類的基礎知識、它的方法和屬性,以及在哪裡可以找到可用變數的列表。

我們將重點介紹WP_Query類提供的引數,專門用於優化SQL查詢,減少執行時間和資源消耗。

當流量和內容有限時,我們通常不會關心查詢的效率。WordPress構建了優化良好的SQL查詢,並提供了一個開箱即用的快取系統。

當流量和網站內容顯著增長時——多達數千個文章——那麼我們必須考慮查詢執行時間。

我們的工具箱

我將向您展示的程式碼已經通過Query Monitor測試,這是一個免費外掛,提供有關查詢效能、觸發掛鉤、HTTP請求、重寫規則等的基本資訊。

作為外掛的替代方案,我們可以強制WordPress儲存查詢資訊,在wp-config.php中宣告以下常量:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
define( 'SAVEQUERIES', true );
define( 'SAVEQUERIES', true );
define( 'SAVEQUERIES', true );

SAVEQUERIES設定為true時,WordPress會在$wpdb->queries陣列中註冊查詢和一堆有用的資訊。因此,呼叫者函式的名稱和每個查詢的執行間隔可以通過在像footer.php這樣的模板檔案中新增以下程式碼來列印:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
if ( current_user_can( 'administrator' ) ) {
global $wpdb;
echo '<pre>';
print_r( $wpdb->queries );
echo '</pre>';
}
if ( current_user_can( 'administrator' ) ) { global $wpdb; echo '<pre>'; print_r( $wpdb->queries ); echo '</pre>'; }
if ( current_user_can( 'administrator' ) ) {
	global $wpdb;
	echo '<pre>';
	print_r( $wpdb->queries );
	echo '</pre>';
}

以下是回顯內容的示例:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
[4] => Array
(
[0] => SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts WHERE 1=1 AND wp_posts.post_type = 'post' AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'private') ORDER BY wp_posts.post_date DESC LIMIT 0, 10
[1] => 0.0163011550903
[2] => require('wp-blog-header.php'), wp, WP->main, WP->query_posts, WP_Query->query, WP_Query->get_posts, QM_DB->query
[trace] => QM_Backtrace Object
( ... )
[result] => 10
)
[4] => Array ( [0] => SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts WHERE 1=1 AND wp_posts.post_type = 'post' AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'private') ORDER BY wp_posts.post_date DESC LIMIT 0, 10 [1] => 0.0163011550903 [2] => require('wp-blog-header.php'), wp, WP->main, WP->query_posts, WP_Query->query, WP_Query->get_posts, QM_DB->query [trace] => QM_Backtrace Object ( ... ) [result] => 10 )
[4] => Array
(
	[0] => SELECT SQL_CALC_FOUND_ROWS  wp_posts.ID FROM wp_posts  WHERE 1=1  AND wp_posts.post_type = 'post' AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'private')  ORDER BY wp_posts.post_date DESC LIMIT 0, 10
	[1] => 0.0163011550903
	[2] => require('wp-blog-header.php'), wp, WP->main, WP->query_posts, WP_Query->query, WP_Query->get_posts, QM_DB->query
	[trace] => QM_Backtrace Object
		( ... )
	[result] => 10
)

如果您想深入研究這個主題,請檢視我們的教程: 編輯wp-config.php。最後,考慮到外掛和內建SAVEQUERIES功能都是開發工具,我們應該在生產環境中關閉它們。

話雖如此,讓我們來看看如何加速WordPress查詢。

WP_Query – 為什麼我們不計算行數

我們可以使用get_posts函式查詢資料庫,該函式返回一個文章陣列或一個新的WP_Query物件例項。在這兩種情況下,我們都可以通過為特定變數設定適當的值來確定查詢的結果。

讓我們從一個示例開始,該示例顯示了通常出現在模板檔案中的常見迴圈:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// The Query
$the_query = new WP_Query( $args );
// The Loop
if ( $the_query->have_posts() ) {
while ( $the_query->have_posts() ) : $the_query->the_post();
// Your code here
endwhile;
} else {
// no posts found
}
/* Restore original Post Data */
wp_reset_postdata();
// The Query $the_query = new WP_Query( $args ); // The Loop if ( $the_query->have_posts() ) { while ( $the_query->have_posts() ) : $the_query->the_post(); // Your code here endwhile; } else { // no posts found } /* Restore original Post Data */ wp_reset_postdata();
// The Query
$the_query = new WP_Query( $args );
// The Loop
if ( $the_query->have_posts() ) {
	while ( $the_query->have_posts() ) : $the_query->the_post(); 
		// Your code here
	endwhile;
} else {
		// no posts found
}
/* Restore original Post Data */
wp_reset_postdata();

$args是一個鍵/值對陣列。這些對被命名為查詢變數,並決定或影響實際的SQL查詢。從外掛查詢資料庫時,我們可能更喜歡使用pre_get_posts過濾器,如下例所示:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function myplugin_pre_get_posts( $query ) {
if ( is_admin() || ! $query->is_main_query() ){
return;
}
$query->set( 'category_name', 'webdev' );
}
add_action( 'pre_get_posts', 'myplugin_pre_get_posts', 1 );
function myplugin_pre_get_posts( $query ) { if ( is_admin() || ! $query->is_main_query() ){ return; } $query->set( 'category_name', 'webdev' ); } add_action( 'pre_get_posts', 'myplugin_pre_get_posts', 1 );
function myplugin_pre_get_posts( $query ) {
  if ( is_admin() || ! $query->is_main_query() ){
	return;
  }
  $query->set( 'category_name', 'webdev' );
}
add_action( 'pre_get_posts', 'myplugin_pre_get_posts', 1 );

這裡要注意的重要一點是$query物件是通過引用傳遞的,而不是通過值傳遞,這意味著查詢引數只是影響現有$query例項。

set方法將一個新的查詢變數新增到查詢規範中,並將強制WordPress從webdev類別中檢索所有文章。這是結果查詢:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
SELECT SQL_CALC_FOUND_ROWS wp_posts.ID
FROM wp_posts
INNER JOIN wp_term_relationships
ON (wp_posts.ID = wp_term_relationships.object_id)
WHERE 1=1
AND ( wp_term_relationships.term_taxonomy_id IN (12) )
AND wp_posts.post_type = 'post'
AND (wp_posts.post_status = 'publish'
OR wp_posts.post_status = 'private')
GROUP BY wp_posts.ID
ORDER BY wp_posts.post_date DESC
LIMIT 0, 10
SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts INNER JOIN wp_term_relationships ON (wp_posts.ID = wp_term_relationships.object_id) WHERE 1=1 AND ( wp_term_relationships.term_taxonomy_id IN (12) ) AND wp_posts.post_type = 'post' AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'private') GROUP BY wp_posts.ID ORDER BY wp_posts.post_date DESC LIMIT 0, 10
SELECT SQL_CALC_FOUND_ROWS wp_posts.ID
FROM wp_posts 
INNER JOIN wp_term_relationships
ON (wp_posts.ID = wp_term_relationships.object_id)
WHERE 1=1 
AND ( wp_term_relationships.term_taxonomy_id IN (12) )
AND wp_posts.post_type = 'post'
AND (wp_posts.post_status = 'publish'
OR wp_posts.post_status = 'private')
GROUP BY wp_posts.ID
ORDER BY wp_posts.post_date DESC
LIMIT 0, 10

在此示例中,LIMIT值已由管理員使用者在閱讀選項中設定,如下圖所示。

WordPress閱讀設定

在自定義查詢中,由於分頁引數, 我們可以設定posts_per_page要從資料庫中檢索的行數。

SQL_CALC_FOUND_ROWS選項強制查詢計算找到的行數。該數字將由SQL函式FOUND_ROWS()返回,如以下示例所示:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
SELECT SQL_CALC_FOUND_ROWS * FROM tbl_name
WHERE id > 100 LIMIT 10;
SELECT FOUND_ROWS();
SELECT SQL_CALC_FOUND_ROWS * FROM tbl_name WHERE id > 100 LIMIT 10; SELECT FOUND_ROWS();
SELECT SQL_CALC_FOUND_ROWS * FROM tbl_name
WHERE id > 100 LIMIT 10;

SELECT FOUND_ROWS();

不幸的是,SQL_CALC_FOUND_ROWS會顯著減慢查詢執行時間。好訊息是我們可以強制WordPress刪除提供未充分使用(且未記錄)no_found_rows變數的選項。

如果省略了SQL_CALC_FOUND_ROWS,則FOUND_ROWS()將返回最大值為LIMIT的行數(MySQL文件中有關此主題的更多資訊)。

在包含數百個文章的WordPress安裝中,以下元查詢耗時0.0107秒:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
SELECT SQL_CALC_FOUND_ROWS wp_posts.ID
FROM wp_posts
INNER JOIN wp_postmeta
ON ( wp_posts.ID = wp_postmeta.post_id )
WHERE 1=1
AND ( ( wp_postmeta.meta_key = 'book_author'
AND CAST(wp_postmeta.meta_value AS CHAR) LIKE '%Isaac Asimov%' ) )
AND wp_posts.post_type = 'book'
AND (wp_posts.post_status = 'publish'
OR wp_posts.post_status = 'private')
GROUP BY wp_posts.ID
ORDER BY wp_posts.post_date DESC
LIMIT 0, 10
SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts INNER JOIN wp_postmeta ON ( wp_posts.ID = wp_postmeta.post_id ) WHERE 1=1 AND ( ( wp_postmeta.meta_key = 'book_author' AND CAST(wp_postmeta.meta_value AS CHAR) LIKE '%Isaac Asimov%' ) ) AND wp_posts.post_type = 'book' AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'private') GROUP BY wp_posts.ID ORDER BY wp_posts.post_date DESC LIMIT 0, 10
SELECT SQL_CALC_FOUND_ROWS wp_posts.ID
FROM wp_posts 
INNER JOIN wp_postmeta
ON ( wp_posts.ID = wp_postmeta.post_id )
WHERE 1=1 
AND ( ( wp_postmeta.meta_key = 'book_author'
AND CAST(wp_postmeta.meta_value AS CHAR) LIKE '%Isaac Asimov%' ) )
AND wp_posts.post_type = 'book'
AND (wp_posts.post_status = 'publish'
OR wp_posts.post_status = 'private')
GROUP BY wp_posts.ID
ORDER BY wp_posts.post_date DESC
LIMIT 0, 10

刪除SQL_CALC_FOUND_ROWS,設定no_found_rows為false,相同的查詢需要0.0006秒。比較有和沒有SQL_CALC_FOUND_ROWS選項的兩個查詢

感謝Query Monitor外掛,我們可以很容易地比較有和沒有SQL_CALC_FOUND_ROWS選項的兩個查詢

wp_post表包含數千行時,查詢執行可能需要幾秒鐘。當我們不需要分頁時,我們應該設定no_found_rowstrue,使查詢執行得更快。

快取或不快取

WordPress提供了一個開箱即用的內建快取系統。雖然快取通常會提高頁面載入速度,但它可能會導致對資料庫執行一些額外的查詢。此外,無論何時執行查詢,都可能會請求一堆不必要的資料。

幸運的是,WordPress允許我們提供三個特定引數來禁用快取:

  • cache_results : 是否快取文章資訊。預設為true。
  • update_post_meta_cache:是否更新文章meta快取。預設為true。
  • update_post_term_cache:是否更新文章term快取。預設為true。

如果啟用了持久快取系統,例如Memcached,我們就不必關心快取引數,因為WordPress預設會將這些引數設定為false。

在任何其他情況下,我們可以使用以下程式碼構建更快的查詢:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function myplugin_pre_get_posts( $query ) {
if ( is_admin() || ! $query->is_main_query() ){
return;
}
$query->set( 'category_name', 'webdev' );
$query->set( 'no_found_rows', true );
$query->set( 'update_post_meta_cache', false );
$query->set( 'update_post_term_cache', false );
}
add_action( 'pre_get_posts', 'myplugin_pre_get_posts', 1 );
function myplugin_pre_get_posts( $query ) { if ( is_admin() || ! $query->is_main_query() ){ return; } $query->set( 'category_name', 'webdev' ); $query->set( 'no_found_rows', true ); $query->set( 'update_post_meta_cache', false ); $query->set( 'update_post_term_cache', false ); } add_action( 'pre_get_posts', 'myplugin_pre_get_posts', 1 );
function myplugin_pre_get_posts( $query ) {
  if ( is_admin() || ! $query->is_main_query() ){
	return;
  }
  $query->set( 'category_name', 'webdev' );

  $query->set( 'no_found_rows', true );
  $query->set( 'update_post_meta_cache', false );
  $query->set( 'update_post_term_cache', false );
}
add_action( 'pre_get_posts', 'myplugin_pre_get_posts', 1 );

當永久快取系統不可用時,不應快取返回少量資料的查詢。

返回的欄位

作為一般規則,我們永遠不應該在資料庫中查詢不必要的欄位。WP_Query類提供的欄位引數,這允許限制返回欄位的ID或 'id=>parent' 欄位。原始檔文件定義fields引數如下:

要返回的欄位。單個欄位或所有欄位(字串),或欄位陣列。’id=>parent’ 使用 ‘id’ 和 ‘post_parent’。預設所有欄位。接受“ids”、“id=>parent”。

fields變數允許'ids''id=>parent',並且預設為 *(任何其他值),儘管您會注意到預設情況下WordPress會在多個查詢中將該值設定為ids。最後,我們可以優化我們的第一個查詢:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?php
$args = array(
'no_found_rows' => true,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
'category_name' => 'cms',
'fields' => 'ids'
);
// The Query
$the_query = new WP_Query( $args );
$my_posts = $the_query->get_posts();
if( ! empty( $my_posts ) ){
foreach ( $my_posts as $p ){
// Your code
}
}
/* Restore original Post Data */
wp_reset_postdata();
?>
<?php $args = array( 'no_found_rows' => true, 'update_post_meta_cache' => false, 'update_post_term_cache' => false, 'category_name' => 'cms', 'fields' => 'ids' ); // The Query $the_query = new WP_Query( $args ); $my_posts = $the_query->get_posts(); if( ! empty( $my_posts ) ){ foreach ( $my_posts as $p ){ // Your code } } /* Restore original Post Data */ wp_reset_postdata(); ?>
<?php
$args = array( 
	'no_found_rows' => true, 
	'update_post_meta_cache' => false, 
	'update_post_term_cache' => false, 
	'category_name' => 'cms', 
	'fields' => 'ids'
);
// The Query
$the_query = new WP_Query( $args );
$my_posts = $the_query->get_posts();

if( ! empty( $my_posts ) ){
    foreach ( $my_posts as $p ){
        // Your code
    }
}
/* Restore original Post Data */
wp_reset_postdata();
?>

當不需要特定欄位時,將返回的欄位限制為 ID。

小結

考慮到查詢速度對於幾百個帖子的小網站來說可能不會帶來巨大的優勢。如果您想為增長做好準備,或者您正在執行一個包含昂貴查詢的大型網站,您應該優化您的 WordPress 查詢。低效查詢會顯著減慢頁面載入速度,但通過一些簡單的調整,您可以大大加快您的網站速度。

評論留言