こんにちは!WordPressの裏側の仕組みやデータベースの挙動に興味を持ってくれて嬉しいです。他のプログラミング言語や一般的なWebフレームワークを触ったことがある人ほど、「WordPressのデータベースって、どうしてこんな構造なんだろう?」と疑問に思うことが多いですよね。
今回は、WordPressの心臓部であるデータベース、特に`wp_posts`テーブルの`post_type`カラムとインデックスの関係について、奥深く、かつ分かりやすく解説していきますね。
ここをクリアすると、WordPressのクエリチューニングの基本がバッチリマスターできますよ。一緒に内部の世界を覗いてみましょう!
—
1. なぜ `wp_posts` の `post_type` がデータベースの生命線なのか?
WordPressのデータベースを覗いたことはありますか?基本インストールを行うと、いくつかのテーブルが生成されますが、中でも最も巨大になり、トラフィックの負荷を受け止めるのが `wp_posts` テーブルです。
このテーブルには、以下のようなあらゆる「コンテンツの器」がごちゃ混ぜで保存されています。
- 投稿 (`post`)
- 固定ページ (`page`)
- 添付ファイル (`attachment`)
- カスタム投稿タイプ (`your_custom_type`)
- リビジョン (`revision`)
- ナビゲーションメニュー等 (`nav_menu_item`)
ここで、他の言語から来た開発者が最初に驚くポイントがあります。それは、「これら全く性質の異なるデータが、1つのテーブルの行(レコード)として平面的に同居している」という点です。
例えば、ブログの公開記事が10万件あり、その裏でリビジョン(下書きの履歴)が50万件蓄積されているとします。このとき、あなたが「公開されている投稿だけを取得したい」と思ってPHP側で `get_posts()` や `WP_Query` を実行すると、MySQL(データベース)は裏側で次のようなSQLを組み立てて実行しています。
SELECT FROM wp_posts WHERE post_type = ‘post’ AND post_status = ‘publish’;
この時、データベースの内部では何が起きているのでしょうか?ここからが今回の本題、クエリプランナーとインデックスの挙動の話です。
—
2. クエリプランナーの裏側と「インデックス」の正体
データベース(MySQL / MariaDB)の中には、クエリプランナー(Query Planner)という非常に優秀な頭脳を持つ管理者がいます。彼は、「どうすれば一番速くお目当てのデータを見つけ出せるか」のルート案内図を計算する役割を持っています。
インデックスがない世界(フルテーブルスキャン)
もし、`wp_posts` テーブルの `post_type` カラムにインデックス(索引)が張られていなかったらどうなるでしょうか?
クエリプランナーは、100万件あるレコードの先頭から一番最後(100万件目)まで、すべてを目視で(上から順に)確認していくことになります。これをフルテーブルスキャン(全表走査)と呼びます。
本に例えるなら、目次も索引もない1000ページの分厚い辞書から、特定の単語が載っているページを探すために、1ページ目から順番にめくっていくようなものです。恐ろしく時間がかかりますよね。
インデックスがある世界(B-Treeによる爆速検索)
ここで登場するのがインデックスです。
WordPressの標準インストール時、実は `wp_posts` テーブルの `post_type` には、単体のインデックスが張られていない場合があります(※バージョンや環境、主要なキーとの複合インデックスの有無によりますが、パフォーマンスチューニングの文脈では非常に重要なポイントです)。
もし `post_type` にインデックス(B-Tree構造)が存在していれば、データベースは一瞬で「あ、`post_type = ‘post’` のデータは、この住所(ポインタ)にまとまっているな」とピンポイントでジャンプできます。
—
3. データの偏りと「カーディナリティ」の罠
ここで、データベースチューニングにおける非常に重要な概念をご紹介します。それがカーディナリティ(Cardinality:値の分散度)です。
- 高カーディナリティ:値の種類が非常に多いもの(例:`ID`, `post_date`, `guid` など)
- 低カーディナリティ:値の種類が少ないもの(例:`post_status` [publish, draft等], そして `post_type` [post, page, custom等])
実は、`post_type` は「低カーディナリティ」なカラムの代表格です。
もし、データベースに登録されている全10万件のデータのうち、9万9千件が `post_type = ‘post’` で、残り1,000件だけが `post_type = ‘custom_event’` だったとしましょう。
この状態で「`post_type = ‘post’` のデータを取ってきて!」と命令すると、クエリプランナーはこう考えます。
「おいおい、全データの99%を取得するなら、インデックスを使ってあちこちのページを行き来するより、最初から上から順番に(フルテーブルスキャンで)読んだほうが速いぞ!」
このように、データの偏り(分布)や取得割合によって、データベースはインデックスを使わない選択をあえてすることがあります。これが、他の言語のORMやSQLに慣れた開発者がハマりやすい「なぜインデックスが効かないのか?」という謎の正体です。
—
4. 実践:特定のカスタム投稿タイプを爆速化する複合インデックス戦略
では、実務の現場で私たちが書くべきコードと、データベースへのアプローチを見ていきましょう。
例えば、あなたが `event` という独自のカスタム投稿タイプを大量に持ち、そのメタ情報(日時など)やステータスで絞り込むクエリを頻繁に発行するシステムを開発しているとします。
良いコード(WP_Query の適切な設計)
// データベースに無駄な負荷をかけないためのスマートなクエリ設計
$args = array(
‘post_type’ => ‘event’, // 絞り込む対象を明確に指定
‘post_status’ => ‘publish’,
‘posts_per_page’ => 10,
‘no_found_rows’ => true, // ページネーション用の総件数計算(FOUND_ROWS)をスキップして高速化!
‘fields’ => ‘ids’, // 投稿オブジェクト全体ではなくIDだけを取得してメモリを節約
);
$event_query = new WP_Query( $args );
if ( $event_query->have_posts() ) {
$event_ids = $event_query->posts;
// ここで必要な処理を行う
}
なぜこのコードが優れているのか?
1. `no_found_rows => true`: これを指定すると、内部でSQLの `SQL_CALC_FOUND_ROWS` が無効化されます。全件数をカウントする処理が消えるため、データベースの負荷が劇的に下がります。
2. `fields => `ids“: 巨大なタイトルや本文、スラッグなどをデータベースから引き出さず、主キーである `ID` のみを取得するため、ネットワーク転送量とメモリ消費量を最小限に抑えられます。
—
5. データベース層でのアプローチ(インデックスの追加)
もし、特定のカスタム投稿タイプ(例: `product` や `event`)のデータ量が何十万件にも膨れ上がり、通常のクエリでは限界が来た場合、データベース管理ツール(phpMyAdminやWP-CLIなど)を使って、カスタムインデックスを検討します。
`wp_posts` において、`post_type` 単体ではなく、よくセットで検索されるカラムとの複合インデックス(Composite Index)を貼るのがプロの技です。
— post_type と post_status を組み合わせた複合インデックスの追加例
ALTER TABLE wp_posts ADD INDEX idx_posttype_status (post_type, post_status);
💡 ここで気をつけたい文法・設計エラー
- インデックスを貼りすぎないこと:
「速くなるなら何でもインデックスを貼ろう!」というのは初心者によくある間違いです。インデックスは、データが新規追加(`INSERT`)や更新(`UPDATE`)されるたびに、索引の木構造も再構築されます。つまり、インデックスを増やしすぎると、データの書き込み速度が致命的に遅くなります。
- カラムの順番が命:
複合インデックス `(A, B)` では、`A` で絞り込むクエリには効きますが、`B` 単体で絞り込むクエリには基本的には効きません。常に「左側から順に条件に含まれているか」を意識してください。
—
まとめ
今回は、`wp_posts` テーブルの `post_type` に焦点を当て、データベースの内部挙動とクエリプランナーの思考回路を解説しました。
- `wp_posts` は多様なデータが同居する巨大なテーブルである。
- `post_type` はカーディナリティ(値の種類)が低いため、データの偏りによってクエリの挙動が変わる。
- PHP側では `no_found_rows` や `fields` を活用してデータベースの負担を減らす。
- 必要に応じて複合インデックスを検討するが、書き込みパフォーマンスとのトレードオフを常に意識する。
この仕組みを理解していれば、今後どれだけデータ量が増えても「なぜ今このクエリが重いのか」「どうすればデータベースが喜ぶ書き方にできるのか」が頭の中でスラスラと描けるはずです。
WordPressの奥深い内部構造をマスターして、ワンランク上のエンジニアを目指していきましょう!