【入門編】wp_postsテーブルのpost_contentカラムにおけるBLOB型とTEXT型の物理ストレージ特性と検索パフォーマンス – WordPress 内部コア・データベース構造とパフォーマンス最適化解析バイブル

こんにちは!WordPressの裏側の仕組みやデータベースの挙動に興味を持ってくれて嬉しいです。他の言語やフレームワークを触ってきた方なら、「WordPressって、なんでひとつのテーブルに何でもかんでもデータを詰め込むんだろう?」って一度は疑問に思ったことがあるはずです。

今回は、WordPressの心臓部であるデータベース、特に投稿データを保存する `wp_posts` テーブルの `post_content` カラム にスポットを当てます。

「記事の本文が入る場所でしょ?」と侮ってはいけません。ここを深く理解するかどうかで、数百万PV規模のメディアサイトを支えられるか、それともアクセス急増でデータベースが撃沈するか分かれ道になります。

ここをクリアすれば、WordPressのパフォーマンスチューニングの基本はバッチリマスターできますよ!一緒にその深淵を覗いていきましょう。

—

1. `wp_posts` と `post_content` の物理構造:TEXT型とInnoDBの秘密

まず、データベース(MySQL / MariaDB)のストレージエンジンに InnoDB を使っているという前提でお話ししますね。

`wp_posts` テーブルの `post_content` カラムのデータ型は、デフォルトで `longtext`(最大4GBまで入る巨大な文字列型)になっています。

ここで、他の言語(例えばRuby on RailsやLaravelなど)の綺麗に正規化されたデータベース設計に慣れている人ほど、こう驚くはずです。
> 「えっ、そんなデカいテキストデータを、メインの投稿テーブルに直接持たせて大丈夫なの?」

実はこれ、データベースの物理ストレージの仕組みを知らないと、思わぬパフォーマンスの罠にハマる原因になります。

行外格納(Off-page storage)の仕組みをイメージしよう

MySQLのInnoDBでは、1つのデータページ(デフォルトで16KB)の中に、テーブルの「1行分のデータ」を基本的に収めようとします。これを「Clustered Index(クラスタ化インデックス)」と呼びます。

しかし、考えてみてください。1記事の `post_content` に、高解像度の画像URLや複雑なブロックエディタ(Gutenberg)のJSONデータが大量に含まれていて、サイズが 50KB もあったとしたらどうでしょう?
16KBのページには到底入り切りませんよね。

そこでInnoDBは、「行外格納(Off-page storage / Overflow)」 という賢い仕組みを使います。

[ wp_posts テーブルのデータページ (16KB) ]
┣ ID (INT)
┣ post_title (VARCHAR)
┗ post_content の先頭数バイト ──(ポインタ)──> [ 溢れたデータを保持する専用の別ページ (Overflow Page) ]
┗ 本文の残りの巨大なデータ (例: 50KB)

InnoDBは、メインのページには小さなプレフィックス(先頭の数バイト)と、あふれた実データがどこにあるかを示す「ポインタ」だけを置いておき、実際の巨大なテキスト本体は別のページ(Overflow Page)に追い出します。

—

2. なぜこれがパフォーマンスに影響するのか?(I/Oの罠)

「なんだ、自動で溢れた分を別の場所にしまってくれるなら安心じゃん!」と思いましたか?
ここからがプログラティブにデータベースを考えるエンジニアの腕の見せ所です。

「記事の一覧取得」で発生する無駄なI/O

例えば、トップページやアーカイブページで「最近の投稿10件のリスト」を表示するコードを書くとします。よくあるクエリですね。

SELECT FROM wp_posts WHERE post_type = ‘post’ LIMIT 10;

ここで `SELECT ` を実行したとき、何が起きているでしょうか?
MySQLは「10件分のレコード」を取得しようとしますが、もし `post_content` が行外格納されている場合、データベースは「本文が格納されている別のページ(Overflow Page)までわざわざ読みに行き、それを結合してメモリに載せる」という重い処理(I/O)を強制されます。

本当は「タイトル」と「公開日時」しか使わないのに、何十KBもある「本文」のデータまでディスクから引っ張り出してきているとしたら……?
これが、記事数が数万件を超えたあたりからサイトが重くなる(I/Oネックに陥る)典型的な原因です。

—

3. 実践:WordPressでこの問題にどう立ち向かうか?

では、この物理構造を理解した上で、私たち開発者はどのようなコードを書くべきでしょうか?

❌ 悪い例:安易な `get_posts()` や `SELECT `

何も考えずにすべてのカラムを取得するのは、データベースに余計な負荷をかけます。

// すべてのカラム(当然 post_content の巨大なデータも含む)を取得してしまう
$posts = get_posts( [
‘numberposts’ => 10,
‘post_type’ => ‘post’,
] );

foreach ( $posts as $p ) {
// タイトルしか使わないのに、裏側では post_content の行外データもロードされている可能性が…!
echo ‘

‘ . esc_html( $p->post_title ) . ‘

‘;
}

⭕ 良い例:必要なカラムだけを抽出し、I/Oを極限まで減らす

WordPressのクエリでは、`fields` 引数や `$wpdb` を駆使して、必要なデータだけをピンポイントで取得するのがプロの技です。

// IDとタイトル、投稿日だけを軽量に取得するカスタムクエリ
global $wpdb;

$recent_titles = $wpdb->get_results(
“SELECT ID, post_title, post_date
FROM {$wpdb->posts}
WHERE post_type = ‘post’
AND post_status = ‘publish’
ORDER BY post_date DESC
LIMIT 10”
);

foreach ( $recent_titles as $row ) {
// post_content に触れないため、余計なオーバーフローページの読み込みが発生しない!
echo ‘

‘ . esc_html( $row->post_title ) . ‘ (‘ . esc_html( $row->post_date ) . ‘)

‘;
}

このように、「データがデータベースの物理層でどう扱われているか」をイメージしながらクエリを書くだけで、サーバーのCPU負荷やディスクI/Oを劇的に削減することができます。

—

4. 陥りがちな文法・設計エラーと注意点

最後に、`post_content` やデータベース構造を扱う際によくやってしまうミスをいくつか挙げておきますね。

1. `post_content` に対して `LIKE` 検索を多用する
`SELECT FROM wp_posts WHERE post_content LIKE ‘%keyword%’` のようなクエリは、インデックスが一切効きません(フルテーブルスキャン)。 さらに、各行の行外データまで舐め回すことになるため、データ量が増えると確実にデータベースがスローダウンします。検索機能を本格的に実装したい場合は、MySQLの全文検索(Full-Text Search)や、Elasticsearchなどの外部検索エンジン、または専用のインデックス用カスタムテーブルを検討しましょう。
2. 文字コード(Collation)のミスマッチによるインデックス無効化
もし独自のカスタムクエリを書く際、テーブルのデフォルト文字コード(例: `utf8mb4_unicode_520_ci` など)と異なる条件で比較を行うと、内部で暗黙の型変換や比較処理が走り、パフォーマンスが悪化します。WordPressのデフォルト設定に忠実に従うのが一番安全です。

—

まとめ

いかがでしたでしょうか?
今回は、`wp_posts` の `post_content` カラムが持つTEXT型の特性と、InnoDBの「行外格納(Off-page storage)」という物理構造について解説しました。

  • 巨大なテキストはメインページから溢れて別の場所(Overflow Page)に保存される。
  • 無駄に `SELECT ` や `get_posts()` で本文まで読み込むと、不要なディスクI/Oが発生してサイトが重くなる。
  • 一覧画面やウィジェットなど、本文が不要な場所では取得するカラムを絞り込む(あるいは必要なデータだけをクエリする)のがエンジニアの流儀。

一見ただのデータベースの仕様に見えますが、ここを知っているだけで、書くコードの品質がプロフェッショナルなレベルに一気に引き上げられます。

今日の学びを活かして、ぜひご自身のWordPressサイトのクエリやコードを見直してみてくださいね。応援しています!

タイトルとURLをコピーしました