2026年6月17日
2026年6月17日
WordPressのDBクエリを最適化してページ生成時間を短縮する方法
ページ生成時間(サーバーがHTMLを作り終えるまでの時間)が長いサイトでは、原因の多くがデータベースへの問い合わせ、つまりDBクエリにあります。闇雲に設定を変える前に「どのクエリが・何回・どれだけ時間を使っているか」を計測し、重いものから順に手を入れるのが近道です。この記事では、計測の方法、WordPressでよくある重いクエリのパターン、WP_Query の書き方とキャッシュによる改善、作業後の確認方法までを順に説明します。
まずページ生成時間とクエリを計測する
改善の前後を比べられるように、最初に数字を取っておきます。見るべき指標は2つです。
- TTFB(最初の1バイトが届くまでの時間):ブラウザの開発者ツールの「ネットワーク」タブで、HTML文書の待機時間として確認できます。コマンドラインなら次のように測れます。
- 1ページあたりのクエリ数とその合計時間:後述の Query Monitor で確認します。
# 同じURLを数回測り、ばらつきを見る
curl -o /dev/null -s -w "TTFB: %{time_starttransfer}s\n" https://example.com/
ページキャッシュが効いている状態で測ると、PHPもDBも動かずに結果が返るため、クエリの改善効果が見えません。計測するときはログイン状態で測るか、キャッシュプラグインの対象外になるURL(クエリ文字列を付けるなど)で測ってください。
Query Monitor で重いクエリを特定する
Query Monitor は、管理バーからそのページで実行されたクエリの一覧、合計時間、呼び出し元のプラグイン・テーマを確認できる開発者向けプラグインです。改善の手がかりになるのは次の3つの画面です。
- 遅いクエリ:一定時間を超えたクエリが強調表示されます。
- 重複クエリ:同じSQLが何度も実行されていれば、ループ内で同じ処理を繰り返している可能性があります。
- コンポーネント別:どのプラグイン・テーマがどれだけクエリを発行しているかが分かり、原因の切り分けに役立ちます。
表示項目の名前はバージョンで変わることがあります(2026年10月現在)。本番環境で常時有効にするとオーバーヘッドがあるため、調査が終わったら停止するか、ステージング環境で使うのが安全です。
プラグインを入れられない環境では、wp-config.php に define( 'SAVEQUERIES', true ); を書くと $wpdb->queries にクエリと所要時間が記録されます。こちらもメモリを消費するので、調査時だけ有効にします。
MySQLのスロークエリログも併用する
Query Monitor は表示したページのクエリしか見えません。サイト全体で遅いクエリを拾うには、MySQL / MariaDB のスロークエリログ(slow_query_log と long_query_time)が有効です。共用サーバーでは設定できない場合もあるので、使えるかどうかはサーバーの管理画面やサポート情報で確認してください。
WordPressでよくある重いクエリのパターン
計測で原因が見えたら、多くは次のどれかに当てはまります。
自動読み込み(autoload)のオプションが肥大化している
wp_options テーブルのうち自動読み込みに指定された行は、すべてのページで最初に一括で読み込まれます。削除済みプラグインの設定や大きなキャッシュデータが残っていると、毎回のリクエストで無駄なデータを読むことになります。WordPress 6.6 以降は autoload 列の値の種類が増えているため、次のように複数の値を対象にして大きい順に確認します(テーブル接頭辞が wp_ 以外なら読み替えてください)。
SELECT option_name, LENGTH(option_value) AS size
FROM wp_options
WHERE autoload IN ('yes', 'on', 'auto-on', 'auto')
ORDER BY size DESC
LIMIT 20;
見覚えのないプラグイン名の行があっても、すぐに削除しないでください。現役のプラグインが使っている場合があるため、名前で由来を調べ、データベースのバックアップを取ってから対応します。サイトヘルスにも自動読み込みオプションの容量に関する項目があるので、あわせて見ておくと判断しやすくなります。
メタ情報での絞り込み・並べ替え
meta_query や meta_value による並べ替えは、wp_postmeta テーブルの値に索引が効きにくいため、投稿数が増えると急に遅くなります。「この条件で絞り込む」用途が頻繁にあるなら、カスタムフィールドではなくタクソノミー(カテゴリーやカスタム分類)で表現した方が、索引の効く結合になり軽く済みます。
ループの中でさらにクエリを発行している
記事一覧のループ内で get_posts() や new WP_Query() を呼ぶと、表示件数の分だけクエリが増えます。Query Monitor で重複クエリが大量に出る場合はこの形を疑い、ループの外でまとめて取得してから使う形に書き換えます。
WP_Query を軽く書く
テーマや自作プラグインで記事を取得している場合、引数を少し変えるだけでクエリが減ります。
'no_found_rows' => true:ページ送りを使わない一覧で、総件数を数える処理を省きます。'fields' => 'ids':IDだけ必要なときに、投稿データ全体を取得しないようにします。'update_post_term_cache' => false/'update_post_meta_cache' => false:タームやメタ情報を使わない一覧で、事前読み込みを省きます。'posts_per_page' => -1(全件取得)は避け、必要な件数に絞ります。'orderby' => 'rand'は件数が多いと重くなるため、多用しないようにします。
さらに、結果がすぐには変わらない重い処理は Transients API で一定時間キャッシュします。次の例は「閲覧数の多い記事5件のID」を1時間保存し、記事が更新されたら破棄するものです。子テーマの functions.php か自作プラグインに書きます。
function mytheme_get_popular_post_ids() {
$ids = get_transient( 'mytheme_popular_ids' );
if ( false === $ids ) {
$query = new WP_Query( array(
'post_type' => 'post',
'posts_per_page' => 5,
'meta_key' => 'views',
'orderby' => 'meta_value_num',
'order' => 'DESC',
'fields' => 'ids',
'no_found_rows' => true,
'update_post_term_cache' => false,
'ignore_sticky_posts' => true,
) );
$ids = $query->posts;
set_transient( 'mytheme_popular_ids', $ids, HOUR_IN_SECONDS );
}
return $ids;
}
// 記事を保存したらキャッシュを破棄する
add_action( 'save_post_post', function () {
delete_transient( 'mytheme_popular_ids' );
} );
views というメタキーは例なので、実際に閲覧数を保存しているキー名に合わせてください。
オブジェクトキャッシュとデータの整理
サーバーで Redis や Memcached が使える場合は、永続オブジェクトキャッシュを導入すると、同じクエリの結果をリクエストをまたいで再利用でき、DBへの問い合わせそのものが減ります。WordPressでは Redis Object Cache などのプラグインで object-cache.php を配置する形が一般的です。対応状況はサーバーによって異なるため、事前に提供元の情報を確認してください。
不要なデータの整理も効果があります。リビジョンが大量に溜まっている場合は wp-config.php の WP_POST_REVISIONS で保存数を制限でき、期限切れのトランジェントは WP-CLI の wp transient delete --expired で削除できます。削除を伴う作業の前には、wp db export などでデータベースのバックアップを取ってください。
テーブルへの索引追加も選択肢ですが、構造の変更を伴うため、ステージング環境で効果と影響を試してから本番に適用します。
改善できたかを確認する
作業のたびに、最初に取った数字と比べます。
- Query Monitor でクエリ数・合計時間・遅いクエリの数が減ったか
curlやブラウザで測ったTTFBが短くなったか(数回測って平均を見る)- 一覧の件数、並び順、ページ送りなど表示内容が変わっていないか
キャッシュを導入した場合は、記事を更新したあとに古い内容が出続けないかも確認します。破棄のタイミングを入れ忘れると、速くなった代わりに更新が反映されないという別の問題が起きます。
まとめ
- 最初にTTFBとクエリ数を計測し、Query Monitor やスロークエリログで重いクエリを特定する
- 自動読み込みオプションの肥大化、メタ情報での絞り込み、ループ内のクエリが代表的な原因
WP_Queryはno_found_rowsやfieldsで必要な分だけ取得し、重い結果は Transients でキャッシュする- 永続オブジェクトキャッシュやデータ整理も有効。削除や構造変更の前にはバックアップを取る
- 改善後は数字と表示内容の両方を確認する