WordPress: WP_Queryはまとめて取得した投稿を個別にキャッシュしている


ちょっと嬉しい発見があったのでメモ。

余分にSQLを発行することでトータルのDBコール回数を劇的に減らせることがある

たとえば次のように10通の投稿へのリンクを作ると、SQLは20回発行される。

// 表示したい投稿ID
$post_ids = array( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 );

// 各投稿へのリンクを作成
foreach ( $post_ids as $post_id ) {
    $out[] = sprintf(
        '<li><a href="%s">%s</a></li>',
        get_permalink( $post_id ),
        get_the_title( $post_id ),
    );
}

これは get_permalink() が内部的に get_post() を発行するため。get_post() は投稿と関連タームを取得するためにDBを2回コールする。get_the_title() はオブジェクトキャッシュからデータを取得するのでDBアクセスは発生しない。

このとき次のように get_permalink() の前にまとめて投稿を取得しておくと、発行される回数は2回で済む。投稿を取得するための1回と、関連タームを取得するための1回。

$post_ids = array( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 );

$q = new WP_Query( array(
    'post_status'            => 'publish',
    'post__in'               => $post_ids,
    'posts_per_page'         => -1,
    'update_post_meta_cache' => false,
) );
unset( $q );

foreach ( $post_ids as $post_id ) {
    $out[] = sprintf(
        '<li><a href="%s">%s</a></li>',
        get_permalink( $post_id ),
        get_the_title( $post_id ),
    );
}

取得してすぐ unset() しているので無駄なようだが、DBコールの数を大きく削減する効果がある。

これは、WP_Queryが取得した投稿を個別にキャッシュしてくれているため。取得するだけで効果があることを示すために unset() を入れたが、放置でもOK。

ソースをたどってみると、次のような流れで投稿を個別にキャッシュに格納していた。なんとなく、まとめて取得したデータはまとめてキャッシュされるかと思っていたけれど、当然ながらこのほうがヒット率が高い。

new WP_Query()
   → __construct()
      → get_post()
         → update_post_caches()
            → update_post_cache()
               → 投稿を個別に wp_cache_add()

なお 'update_post_meta_cache' => false は、ついでに投稿メタを取得しておくためのSQL発行を抑制する。SQL1回だけだが節約にはなる。

'update_post_term_cache' => false を加えてしまうと逆効果。get_the_category() のなかでタームを取得しているため。ただしパーマリンクの構造によっては

なんにせよget_permalink()にかぎらず、このテクニックは覚えておきたい。


タグ:

Posted by

on