2026年5月20日
2026年5月20日
WordPressのデータベースクエリを最適化する方法
はじめに
WordPressのパフォーマンス問題の多くはDBクエリに起因します。WP_Query の引数の見直し・不要なクエリの削減・インデックスの追加によって、ページあたりのクエリ時間を大幅に短縮できます。
症状・原因
- ページ表示が遅く、DBのCPU使用率が高い
WP_Queryで大量の投稿を取得している- 同じクエリが繰り返し実行されている
- メタクエリやタクソノミークエリが遅い
解決手順
ステップ1:クエリ数とスロークエリを確認する
// wp-config.php: クエリログを有効化(開発環境のみ)
define('SAVEQUERIES', true);
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
// functions.php: フッターにクエリ統計を表示
function mytheme_show_query_stats(): void {
if (!SAVEQUERIES || !current_user_can('manage_options')) {
return;
}
global $wpdb;
$total_time = array_sum(array_column($wpdb->queries, 1));
echo sprintf(
'<!-- Total queries: %d | Total time: %.4fs -->',
$wpdb->num_queries,
$total_time
);
// スロークエリ(0.05秒以上)を表示
foreach ($wpdb->queries as $query) {
if ($query[1] > 0.05) {
error_log('[SLOW QUERY] ' . $query[1] . 's: ' . $query[0]);
}
}
}
add_action('wp_footer', 'mytheme_show_query_stats');
ステップ2:WP_Queryを最適化する
// functions.php
// ❌ 遅い: デフォルトは count クエリも実行される
$query = new WP_Query(['post_type' => 'post', 'posts_per_page' => 10]);
// ✅ 速い: 不要な処理を無効化
$query = new WP_Query([
'post_type' => 'post',
'posts_per_page' => 10,
'no_found_rows' => true, // SQL_CALC_FOUND_ROWS を省略(ページネーション不要時)
'update_post_meta_cache' => false, // 投稿メタのプリロードをスキップ
'update_post_term_cache' => false, // タクソノミーのプリロードをスキップ
'fields' => 'ids', // IDのみ取得(投稿データ不要時)
]);
// IDだけ取得して後から必要なデータのみ処理
$post_ids = $query->posts; // int[]
// メタクエリの最適化
// ❌ 遅い: meta_value での検索(インデックスなし)
$query = new WP_Query([
'meta_query' => [
['key' => '_event_date', 'value' => '2024-01-01', 'compare' => '>='],
],
]);
// ✅ 速い: meta_value_num を使う(数値は適切な型で)
$query = new WP_Query([
'meta_key' => '_event_timestamp',
'meta_value' => time(),
'meta_compare' => '>=',
'meta_type' => 'NUMERIC',
'orderby' => 'meta_value_num',
]);
ステップ3:wpdbで直接クエリを書く
// functions.php
// 複雑な集計はWP_Queryより直接SQLが速い
function mytheme_get_post_view_counts(array $post_ids): array {
global $wpdb;
if (empty($post_ids)) {
return [];
}
$placeholders = implode(',', array_fill(0, count($post_ids), '%d'));
// prepare() でSQLインジェクション対策
$sql = $wpdb->prepare(
"SELECT post_id, meta_value
FROM {$wpdb->postmeta}
WHERE meta_key = '_view_count'
AND post_id IN ($placeholders)",
...$post_ids
);
$results = $wpdb->get_results($sql, ARRAY_A);
return array_column($results, 'meta_value', 'post_id');
}
ステップ4:カスタムインデックスを追加する
// functions.php: テーマ有効化時にインデックスを追加
function mytheme_add_db_indexes(): void {
global $wpdb;
// postmetaのmeta_keyにインデックスが存在するか確認
$index_exists = $wpdb->get_var(
"SHOW INDEX FROM {$wpdb->postmeta}
WHERE Key_name = 'mytheme_event_date_idx'"
);
if (!$index_exists) {
// よく検索するメタキーにインデックスを追加
$wpdb->query(
"ALTER TABLE {$wpdb->postmeta}
ADD INDEX mytheme_event_date_idx (meta_key(191), meta_value(20))"
);
}
}
add_action('after_switch_theme', 'mytheme_add_db_indexes');
ステップ5:クエリをキャッシュしてDB負荷を減らす
// functions.php
// Transient APIで結果をキャッシュ(Redisがない場合)
function mytheme_get_category_post_counts(): array {
$cache_key = 'category_post_counts';
$cached = get_transient($cache_key);
if ($cached !== false) {
return $cached;
}
global $wpdb;
$results = $wpdb->get_results(
"SELECT t.name, t.slug, COUNT(tr.object_id) as count
FROM {$wpdb->terms} t
JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
JOIN {$wpdb->term_relationships} tr ON tt.term_taxonomy_id = tr.term_taxonomy_id
WHERE tt.taxonomy = 'category'
GROUP BY t.term_id
ORDER BY count DESC",
ARRAY_A
);
set_transient($cache_key, $results, 6 * HOUR_IN_SECONDS);
return $results ?: [];
}
// カテゴリ変更時にキャッシュを削除
add_action('edited_category', function(): void {
delete_transient('category_post_counts');
});
注意事項
no_found_rows => trueを使うとページネーションが機能しなくなります。WP_Query::found_postsが0になるため、一覧表示でページネーションが不要な場合のみ使用してくださいwpdb->prepare()を必ず使い、ユーザー入力を直接SQLに埋め込まないでください- インデックス追加はテーブルロックが発生します。大規模サイトでは低トラフィック時間帯に実行してください
まとめ
no_found_rows/update_post_meta_cache/update_post_term_cache を false にするだけでクエリが大幅に減ります。複雑な集計には wpdb->prepare() で直接SQLを書き、結果は set_transient でキャッシュします。SAVEQUERIES でクエリ数を可視化して、スロークエリを特定するところから始めましょう。