WordPressの「重い」を解剖する:wp_postsのインデックスとクエリ最適化の真実
WordPressの管理画面で投稿一覧を開いた瞬間、プログレスバーが止まったように感じることはないか? 多くのエンジニアはこれを「プラグインのせい」や「サーバーのスペック不足」として片付けるが、それはWordPressの心臓部であるデータベース構造への理解不足を露呈しているに過ぎない。
今日は、なぜ`WP_Query`が時に凶器となるのか、そしてDBレベルでどう最適化すべきかを、コアコントリビューターの視点から紐解く。
—
1. なぜ「投稿一覧」は遅延するのか:インデックスの盲点
`wp_posts` テーブルの構造を `DESCRIBE wp_posts;` で確認したことはあるだろうか。
+—————-+———————+——+—–+———————+—————-+
| Field | Type | Null | Key | Default | Extra |
+—————-+———————+——+—–+———————+—————-+
| ID | bigint(20) unsigned | NO | PRI | NULL | auto_increment |
| post_author | bigint(20) unsigned | NO | MUL | 0 | |
| post_date | datetime | NO | MUL | 0000-00-00 00:00:00 | |
| post_type | varchar(20) | NO | MUL | post | |
| post_status | varchar(20) | NO | MUL | publish | |
+—————-+———————+——+—–+———————+—————-+
WordPressのクエリは、基本的に `post_type` と `post_status` を条件に検索を行う。ここで重要なのは、「複合インデックスが貼られていない」という事実だ。
`INDEX(post_type, post_status, post_date)` のような複合インデックスが存在すれば、MySQLは一瞬で対象行を特定できる。しかし、デフォルトでは個別のインデックスしか存在しないため、MySQLのオプティマイザは「どれを使うべきか」を迷い、結果としてフルスキャンに近い挙動(Filesort)を引き起こす可能性がある。
—
2. 実務で「クエリの重さ」を特定する魔法のコード
まず、どのクエリがボトルネックになっているかを可視化せよ。以下のコードを `functions.php` またはデバッグ用のMUプラグインに仕込み、クエリの発行元と実行時間を測定する。
/
- クエリ実行時間を測定し、閾値を超えたらログに出力する
- 開発環境のデバッグバー等で確認することを推奨
/
add_filter(‘query’, function($query) {
global $wpdb;
$start = microtime(true);
// 実際に実行される直前にフック
return $query;
});
add_action(‘query’, function($query) {
if (defined(‘SAVEQUERIES’) && SAVEQUERIES) {
// ここで $wpdb->queries を監視し、特定の閾値(例: 0.1秒)を超えたSQLを抽出
// 現場では Query Monitor プラグインの使用を推奨するが、
// 自前で実装する場合は slow_query_log を有効にするのが鉄則
}
});
—
3. 現場で使える「SQL最適化」と設計の極意
管理画面の投稿一覧が重い場合、`pre_get_posts` フックを用いてクエリを軽量化するのが定石だ。無駄なメタデータの取得や、不必要なJOINを排除せよ。
NGな設計例:
投稿一覧で「カスタムフィールドの値」で並び替えを行おうとして、`posts` テーブルと `postmeta` テーブルをJOINさせる。これはデータ量が増えた瞬間に致命的なパフォーマンス低下を招く。
改善された設計パターン(プロダクションコード):
/
- 管理画面の投稿一覧クエリを最適化する
/
add_action(‘pre_get_posts’, function($query) {
if (!is_admin() || !$query->is_main_query()) {
return;
}
// 特定の投稿タイプにおいて、不要な計算を抑制する
if ($query->get(‘post_type’) === ‘my_custom_post’) {
// ページネーションの計算(found_posts)を抑制して速度を稼ぐ
$query->set(‘no_found_rows’, true);
// メタデータのJOINを避けるために、メタデータ用のインデックスを貼るか、
// そもそも一覧に表示させない設計にする
$query->set(‘update_post_meta_cache’, false);
$query->set(‘update_post_term_cache’, false);
}
});
—
4. エンジニアが守るべき3つの鉄則
1. `no_found_rows` を活用せよ: 投稿数が数万件を超える場合、`SQL_CALC_FOUND_ROWS` (総件数の計算) が最も重い処理となる。ページネーションが不要なら、迷わず `true` に設定せよ。
2. インデックスの追加を恐れるな: データベースの設計は「変更可能」である。特定のカラム(例:`post_status` + `post_type`)で絞り込む頻度が高いなら、直接 `ALTER TABLE` で複合インデックスを追加せよ。WP-CLIの `wp db query` を使えば一瞬だ。
3. キャッシュ層を疑え: データベースに到達する前に、`Object Cache` (Redis/Memcached) が機能しているか確認せよ。`wp_cache_get` がヒットしていれば、DBクエリ自体がバイパスされる。
まとめ
WordPressのパフォーマンスチューニングとは、「MySQLが迷わないための道筋を作ること」に他ならない。インデックス構造を理解し、`WP_Query` の背後で何が起きているかを想像できるエンジニアこそが、真にスケーラブルなシステムを構築できる。
次に重いクエリに出会ったとき、プラグインを削除する前に、`EXPLAIN` コマンドを叩く勇気を持ってほしい。そこには、データベースがあなたに伝えようとしている「最適化のヒント」が必ず隠されているはずだ。