WP_Queryが重いときは、速そうな引数より先に計測する
WP_Queryの高速化は、計測 → 取得量 → 総件数 → 取得項目 → 追加クエリ → 条件・並び順 → キャッシュ → 再計測の順で進めます。
まず表示件数を必要最小限にし、ページ送りがない一覧だけno_found_rowsを検討します。IDだけで目的を満たせる処理ではfields => ‘ids’も候補です。ただし、効果は投稿数、条件、表示内容、キャッシュ環境で変わるため、変更前後を同じ条件で比べてください。
「WP_Queryが重い」と決めつけず、同じ条件で測る
遅さの原因が画像、外部API、PHP、サーバー応答なら、クエリ引数を変えても改善しません。検証環境でQuery Monitorなどを使い、時間の長いクエリ、同じSQLの繰り返し、呼び出し元を確認します。変更前の時間とクエリ数も記録してください。
Query Monitorを有効化したら、閲覧権限のある管理者で対象ページを開き、管理バーの診断メニューから「Queries」や「Queries by Component / Caller」を確認します。表示名は言語やバージョンで異なります。対象テンプレートを特定し、ページ生成時間、データベースクエリの合計時間、クエリ数、遅いSQLを記録してから1項目だけ変更します。計測後は不要な診断機能を止め、公開画面へSQLやデバッグ値を残さないでください。
| 見るもの | 分かること | 次の判断 |
|---|---|---|
| ページ全体の応答時間 | 遅さを再現できているか | 同じURL・同じデータで変更前後を比べる |
| WP_Query由来のSQL | 時間の長い取得や繰り返しがあるか | 対象のテンプレートと$argsを特定する |
| クエリ数 | ループ内の追加取得が増えていないか | N+1やキャッシュ準備を確認する |
| 画像・外部通信・PHP処理 | SQL以外が支配的ではないか | WP_Query以外の改善へ切り替える |
Query Monitorで見る項目と切り分け手順は、次の記事で詳しく確認できます。
取得と処理の無駄を順番に減らす
計測結果を見ながら、条件を1つずつ変えます。
1. 取得件数を表示に必要な数まで減らす
関連記事を4件表示するなら、posts_per_pageも4件にします。20件取得してPHP側で捨てる処理は避けます。
特にposts_per_page => -1は、条件に合う投稿を全件取得する指定です。本当に全件が必要でしょうか。公開ページでは、ページ分割、件数上限、管理用のバッチ処理への分離を先に検討してください。全件取得の注意点では、代わりに選べる方法まで整理しています。
2. ページ送りがない一覧だけ総件数計算を省く
関連記事やサイドバーの新着一覧のように、ページ送りを作らない表示ではno_found_rows => trueを検討します。これは、ページネーションに必要な総件数を求めない指定です。
通常のページ番号や「次のページ」のリンクをmax_num_pagesから作る場合は、条件に合う総件数の計算が必要です。これらを使わない小さな一覧だけ、総件数計算を省けるか確認します。別方式で次のデータの有無を判定する実装まで、総件数が必須という意味ではありません。
3. IDだけで目的を満たせるときはfieldsを検討する
別の処理へ投稿IDを渡すだけなら、fields => ‘ids’でIDだけを受け取れます。たとえば、後続処理へID一覧を渡す、表示済みIDをpost__not_inへ渡す、一定件数ずつ更新する管理処理などです。
ただし、通常の記事カードでタイトル、URL、アイキャッチ、カスタムフィールドを表示するなら、後から投稿情報を取り直すことがあります。
ID取得後に投稿ごとに情報を取り直すと、追加クエリが増えることがあります。fieldsはIDだけで処理が終わる場面に限定します。
4. ループ内の追加取得とキャッシュ準備を確認する
一覧本体のクエリが1回でも、ループ内で投稿ごとに追加のメタ情報やターム情報を取得し、件数に応じてクエリ数が増える状態をN+1と呼びます。WP_Queryには、投稿メタやタームのキャッシュを準備するupdate_post_meta_cache、update_post_term_cacheがあります。表示に使わないことが明確ならfalseを検討しますが、機械的に無効にすることはお勧めしません。無効にした後で各投稿のメタ値やカテゴリーを読むと、かえって追加取得が増える場合があります。
キャッシュ関連引数をまとめてコピーせず、一覧に使う情報を確認してから1つずつ変更します。タイトルとURLだけの一覧でも、パーマリンクに%category%が含まれる場合やプラグインのフィルターで、カテゴリーやメタ情報を参照することがあります。見える項目だけで「キャッシュ準備は不要」とは判断しません。
5. 絞り込み条件と並び順を必要以上に複雑にしない
複数のmeta_query、広いLIKE検索、多数のOR、NOT EXISTS、大きなpost__in、カスタムフィールド値での並び替え、orderby => ‘rand’などは、データ量やインデックス、組み合わせによって負荷が増えます。
ただし、特定の引数を見ただけで「必ず遅い」とは断定できません。条件を一つずつ外して計測し、カテゴリーやタグで表せる分類を投稿メタで持っていないか、必要のない並び替えをしていないかを見直します。meta_queryの基本とランダム表示の注意点も、対象の条件に合わせて確認してください。
必要なデータを、必要な件数だけ取得することが基本です。検索範囲や絞り込みを必要以上に広げず、表示に使わないデータや処理を減らしてください。取得項目・カスタムフィールド・ランダム表示・分類条件を個別に見直す場合は、次の記事へ進みます。
ページ送りのない小さな一覧を見直す例
次は、ページ送りのない新着5件をリンク付きで表示する例です。クラシック子テーマのtemplate-parts/latest-posts.phpへ保存し、テンプレートのメインループ内でthe_post()の後にget_template_part( ‘template-parts/latest-posts’ );を呼びます。投稿本文やfunctions.phpの直下に貼るコードではありません。
比較ではno_found_rowsだけを変えます。メタ・タームのキャッシュ準備は既定のままにし、表示に必要な情報を不用意に省かない形です。
現在のWP Query Builder実画面では、詳細モードから「投稿タイプ: post」「ステータス: publish」「件数: 5」「並び順: DESC / date」を指定し、Performance / Cacheでno_found_rowsとignore_sticky_postsをtrue、自動最適化をオフにしました。そこで生成された次の$argsは編集せず使用しています。
クラシックテーマのテンプレート(またはテンプレートパーツ)で、一覧を表示したい位置に置く例です。投稿本文やfunctions.phpへ、そのまま貼るコードではありません。
<?php$args = array( 'post_type' => 'post', 'post_status' => 'publish', 'posts_per_page' => 5, 'order' => 'DESC', 'orderby' => 'date', 'no_found_rows' => true, 'ignore_sticky_posts' => true); $latest_posts = new WP_Query( $args ); if ( $latest_posts->have_posts() ) { echo '<ul>'; while ( $latest_posts->have_posts() ) { $latest_posts->the_post(); printf( '<li><a href="%s">%s</a></li>', esc_url( get_permalink() ), esc_html( get_the_title() ) ); } echo '</ul>';} else { echo '<p>公開済みの投稿はありません。</p>';} wp_reset_postdata();Builderが生成したのは$argsまでです。続くWP_Query、表示ループ、0件時の文、エスケープ、wp_reset_postdata()は用途に合わせて手動で追加しています。
公開日時の降順にし、先頭固定による特別扱いを外しています。ただし、同じ公開日時の投稿同士まで順番を固定する例ではありません。ignore_sticky_postsは固定投稿の除外ではありません。最後に投稿データを元へ戻すため、後続のタイトルやリンクも確認します。
no_found_rowsをfalseにしたものが比較用の変更前、上のtrueが変更後です。どちらも表示は最大5件ですが、trueではfound_postsやmax_num_pagesを総件数・総ページ数として使えません。ページ送りや「全○件」を追加するときは、この指定を見直してください。
同じ5件が出たまま、総件数計算だけが減る例
表示例はローカルWordPressの公開済み通常投稿23件から、新しい5件(ID82・81・80・79・15)を出したものです。no_found_rowsを切り替えても、表示する投稿と並び順は変わりません。

本文のPHPから新着5件を表示。no_found_rowsをfalseにした場合も同じ5件・同じ順番です。表示される投稿が変わる変更とは分けて比較します。
同じローカル環境(WordPress 7.1 / PHP 8.4.20 / MariaDB 11.8.6、永続オブジェクトキャッシュなし)で、変更前後を交互に10リクエストずつ比較した結果が次の表です。測ったのはPHPの取得・表示ループ・リセットまでで、ページ全体の応答時間やブラウザーの描画時間ではありません。
| 比較項目 | false(変更前) | true(変更後) |
|---|---|---|
| 表示した投稿ID | 82・81・80・79・15 | 82・81・80・79・15 |
| found_posts / max_num_pages | 23 / 5 | 0 / 0(総数は未取得) |
| リクエスト内の初回SQL数 | 6回 | 5回 |
| うちSELECT FOUND_ROWS() | 1回 | 0回 |
| 初回の一覧生成時間の中央値 | 1.623ms | 1.448ms |
| 初回時間の最小〜最大 | 1.020〜55.056ms | 1.104〜5.928ms |
| 同じリクエストで再度生成したときのSQL数 | 0回 | 0回 |
この小さな例で分かるのは、総件数を求めるSQLが1回減り、表示結果が変わらないことです。時間にはばらつきがあり、範囲も重なっています。この差から本番でも体感できる高速化や一定の改善率を保証できません。同じリクエストの2回目はキャッシュが使われ、どちらもSQLが0回でした。初回とキャッシュ利用時を混ぜず、自分のサイトのデータ量で測り直してください。
コード側の処理を切り分けた後、本番との速度差を調べる段階では、PHP・キャッシュ・バックアップなどの運用条件も確認します。Xserverの公式情報を比較材料にできますが、サーバー変更だけで改善するとは限りません。
複雑な$argsは、まず構造を整理する
条件を継ぎ足して配列の役割が分からなくなった場合は、WP Query Builderで$argsを組み直せます。データベースやサーバーを直接速くする機能ではないため、生成後に不要な条件を減らし、変更前後を計測してください。
取得件数、総件数計算、返り値の範囲を見直したら、変更候補の$argsを一度整理します。
高速化候補の$argsを整理する
posts_per_page、no_found_rows、fields、キャッシュ関連引数を見比べてコードを作れます。生成後は表示結果と処理時間を必ず再計測してください。
最後にキャッシュと再計測を考える
取得件数やループ内処理を見直した後も同じクエリを繰り返すなら、キャッシュを検討します。保存期間と失効条件を決め、変更前と同じ条件でもう一度測ります。
6. クエリを小さくした後でキャッシュを検討する
同じ条件の一覧を繰り返し作り、元データが頻繁に変わらないなら結果の再利用を検討します。Transients APIは、計算結果やID一覧などを期限付きで一時保存し、同じ処理の繰り返しを減らすWordPressの仕組みです。
永続オブジェクトキャッシュの有無を確認し、Transientsを使うなら投稿タイプ、カテゴリー、ページ番号、言語など結果を変える条件をキーへ含めます。
保存した値が見つからなければWP_Queryを実行して再生成し、投稿の公開・更新・削除で結果が変わる時は、関係するTransientを削除します。保存期間内でも値が消えることがあるため、常に再生成できる処理を残してください。権限やユーザーによって結果が違う一覧は、同じキャッシュを共有しないようにします。
7. 表示結果を保ったまま速くなったか再計測する
変更後は、時間だけでなく、表示件数、並び順、ページ送り、先頭固定表示、カテゴリー、カスタムフィールドが変わっていないか確認します。
キャッシュを追加した場合は、キャッシュがない初回表示、キャッシュが使われる2回目以降、投稿を更新した直後の3つを分けて確認してください。高速化の確認順
- 1同じURL・同じ投稿データで変更前の時間とクエリ数を記録する
- 2取得件数、総件数、取得項目、追加クエリを一つずつ見直す
- 3条件と並び順を簡単にできないか確認する
- 4必要な場合だけキャッシュを追加する
- 5変更後の時間、クエリ数、表示結果、更新直後の挙動を再確認する
WP_Queryの計測とキャッシュの公式資料
引数、キャッシュ、Transients、Query Monitorの仕様は公式資料で確認できます。
公式リンク
WP_Query高速化の公式資料
公開前の最終確認
- 変更前後を同じURL・投稿データで計測した
- 取得件数、総件数、fields、ループ内の追加取得を目的に合わせた
- ページ送りがある一覧へno_found_rowsを入れていない
- キャッシュのキー、失効、再生成を決めた
- 件数、並び順、ページ送り、更新直後の表示を確認した
この記事の検証情報
検証環境
- WordPress
- 7.1.4
- PHP
- 8.3.36
- テーマ
- wpq-media + DDEV
- 対象
- カスタム投稿 / taxonomy / meta_query
更新履歴
- 2026年9月23日 初回公開
この記事を活用する
あとで見返す・共有する
お気に入りに保存できます。役に立ったポイントは、いいねや共有で教えてください。







