こんにちは!WordPressの裏側の仕組みやデータベースの挙動に興味を持ってくれて嬉しいです。他のプログラミング言語からWordPressに入ってきた開発者の中には、「なんだか思ったようにクエリが早くならないな…」と悩んだ経験がある方も多いのではないでしょうか。
今回は、WordPressの検索や絞り込みでよく使う `WP_Query` の `tax_query`(タクソノミー検索) に焦点を当てます。
「複数のカテゴリーやタグで絞り込むと、なぜかデータベースが重くなる…」その原因は、内部で発行される重いJOIN(結合)とサブクエリにあります。ここをクリアできれば、WordPressのデータベース設計の本質がバッチリ見えてきますよ。一緒に優しく、深く紐解いていきましょう!
—
1. `tax_query` の裏側で何が起きているのか?(JOINのコスト)
まずは、WordPressがデータベースとどう会話しているのか、その裏側を覗いてみましょう。
例えば、「『news』と『tech』という2つのカテゴリーに両方属する投稿を取得したい」という要件があったとします。初心者のうちは、次のような `WP_Query` を書きたくなりますよね。
$args = array(
‘post_type’ => ‘post’,
‘tax_query’ => array(
‘relation’ => ‘AND’,
array(
‘taxonomy’ => ‘category’,
‘field’ => ‘slug’,
‘terms’ => array( ‘news’ ),
),
array(
‘taxonomy’ => ‘category’,
‘field’ => ‘slug’,
‘terms’ => array( ‘tech’ ),
),
),
);
$query = new WP_Query( $args );
このコード自体は非常に直感的で書きやすいのですが、データベース(MySQL)の視点では、実はなかなか負荷の高い処理が行われています。
内部で発行されるSQLのイメージ
WordPressは、上記のリクエストを受け取ると、次のようなSQLを組み立ててデータベースに投げます(※構造を分かりやすく簡略化しています)。
SELECT SQL_CALC_FOUND_ROWS wp_posts.
FROM wp_posts
JOIN wp_term_relationships AS tr1 ON (wp_posts.ID = tr1.object_id)
JOIN wp_term_taxonomy AS tt1 ON (tr1.term_taxonomy_id = tt1.term_taxonomy_id)
JOIN wp_term_relationships AS tr2 ON (wp_posts.ID = tr2.object_id)
JOIN wp_term_taxonomy AS tt2 ON (tr2.term_taxonomy_id = tt2.term_taxonomy_id)
WHERE 1=1
AND (tt1.taxonomy = ‘category’ AND tt1.term_id = 10)
AND (tt2.taxonomy = ‘category’ AND tt2.term_id = 20)
AND wp_posts.post_type = ‘post’
AND wp_posts.post_status = ‘publish’
GROUP BY wp_posts.ID;
おっと、見てください!条件を2つ(`news` と `tech`)指定しただけで、同じテーブル(`wp_term_relationships` と `wp_term_taxonomy`)を2回ずつ、合計4回も JOIN(結合)していますよね。
これがなぜボトルネックになるのか?
タクソノミーの条件が増えれば増えるほど、JOINの数が増え、MySQLが一時的な仮想テーブルを作るためのメモリ(`tmp_table_size` や `max_heap_table_size`)を大量に消費します。さらに、データ量が何十万件と膨れ上がったサイトでは、インデックスが十分に効かずにテーブル全体をスキャン(フルテーブルスキャン)する原因になってしまうのです。
—
2. サブクエリ(`IN`句)や多重JOINを避ける設計術
「じゃあ、タクソノミーで絞り込むときは諦めるしかないの?」いいえ、そんなことはありません!
実務の現場でパフォーマンスを極限まで高めたいとき、私たちがよく使うアプローチの1つが、「カスタムフィールド(メタデータ)や専用のフラグ構造、あるいはリレーションの事前解決」 ですが、まずは `tax_query` を使ったままスマートに挙動を軽くする、あるいは構造を見直すコツを押さえましょう。
ここでは、「複雑な `tax_query` のネストを避け、単一のタームID配列やシンプルな条件に落とし込む」 という設計アプローチを解説します。
改善アプローチ:あらかじめPHP側で対象の投稿IDを確定させる
もし絞り込み条件が複雑になり、MySQLのJOINがパンクしそうであれば、一時的に `get_objects_in_term()` などの軽量な関数を使って、対象となるオブジェクトID(投稿ID)をあらかじめ取得してしまうという手があります。
// 1. ‘news’ (term_id: 10) に属する投稿IDを取得
$posts_in_news = get_objects_in_term( 10, ‘category’ );
// 2. ‘tech’ (term_id: 20) に属する投稿IDを取得
$posts_in_tech = get_objects_in_term( 20, ‘category’ );
// 3. 両方に含まれるID(積集合)をPHP側で計算
$common_post_ids = array_intersect( $posts_in_news, $posts_in_tech );
// もし該当がなければ早期リターン(余計なWP_Queryを走らせない!)
if ( empty( $common_post_ids ) ) {
$common_post_ids = array( 0 ); // 該当なしを示すプレースホルダー
}
// 4. post__in を使って、重いJOINを回避した爆速クエリを発行!
$args = array(
‘post_type’ => ‘post’,
‘post__in’ => $common_post_ids,
‘orderby’ => ‘date’,
‘order’ => ‘DESC’,
);
$query = new WP_Query( $args );
なぜこの方法が速いのか?
`get_objects_in_term()` は、内部でキャッシュ(Object Cache)が効きやすく、純粋にリレーションテーブルからIDの配列をサクッと引いてきます。
そして、その後の `WP_Query` では、面倒な JOIN を一切行わず、`WHERE ID IN (12, 45, 89…)` というプライマリキー(主キー)ベースの超高速な検索に置き換えることができます。
MySQLにとって、主キー(`ID`)に対する `IN` 検索は、インデックスが最も効率よく働く得意分野です。
—
3. 陥りやすい文法・設計エラーと注意点
ここで、初学者や中級者がやりがちな「落とし穴」についても触れておきますね。ここを知っておくだけで、現場でのトラブルを未然に防げます。
エラー1: `post__in` に空の配列を渡してしまう
先ほどのコードで `if ( empty( $common_post_ids ) )` のチェックをサボって空の配列をそのまま `post__in` に渡してしまうと、WP_Queryは「条件なし」と解釈してしまい、データベース内の全公開投稿(最悪の場合は非公開なども含めて)を引っ張ってくるという恐ろしい挙動(全件取得バグ)を引き起こします。
必ず「該当がない場合は `array( 0 )` などの存在しないIDをセットする」という防衛的プログラミングを意識してください。
エラー2: 演算子の組み合わせ(`relation`)の過剰なネスト
`tax_query` 内で `relation => ‘OR’` と `relation => ‘AND’` を何重にもネストさせると、SQLの解釈が複雑になり、MySQLのオプティマイザが最適なインデックスを選択できなくなります。
「どうしても複雑な絞り込みが必要な場合は、検索インデックスサーバー(Elasticsearch等)を検討する」あるいは「WordPressの標準機能の範囲内でフラットな構造に設計し直す」という引き出しを持っておくことが、シニアエンジニアへの第一歩です。
—
まとめ
今回は `tax_query` の内部挙動と、JOINコストを回避するためのアプローチについて解説しました。
- 内部の仕組み: 複雑な `tax_query` は、同じテーブルを何度も JOIN するため、データ量が増えるとパフォーマンスが低下しやすい。
- 解決の糸口: PHP側で事前にIDを絞り込み、`post__in`(プライマリキー検索)を活用することで、データベースの負荷を劇的に軽減できる。
- 注意点: 空の配列を渡したときの挙動など、防衛的なコードを書くことを忘れない。
「なぜこの書き方をするとデータベースに優しいのか?」という視点を持てるようになると、書くコードの質が劇的に変わります。ここをクリアできれば、あなたのWordPress開発スキルは確実に次のステージに進んでいますよ。
明日からのコーディングで、ぜひ意識してみてくださいね!