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_cachefalse にするだけでクエリが大幅に減ります。複雑な集計には wpdb->prepare() で直接SQLを書き、結果は set_transient でキャッシュします。SAVEQUERIES でクエリ数を可視化して、スロークエリを特定するところから始めましょう。

お気軽にご相談ください

お見積りへ お問い合わせへ