2026年6月27日

2026年6月27日

MySQL 8の新機能をWordPressで活用する方法(EXPLAIN ANALYZE・不可視インデックス・ウィンドウ関数)

MySQL 8 系には、WordPress サイトの運用で役に立つ機能がいくつも追加されています。ただし WordPress 本体が自動で使ってくれるわけではありません。WordPress コアは古い MySQL でも動くように書かれているため、新機能の恩恵を受けるのは主に「サイト管理者が DB を調べる・調整するとき」と「自分のサイト専用のコードを書くとき」です。

この記事では、WordPress のデータベースに対して実際に使える機能を、目的別に SQL の例つきで紹介します。

前提:バージョンの位置づけ

  • MySQL 8.0 は2026年4月にサポートが終了しています。これから使うなら長期サポート版の MySQL 8.4 LTS が現実的です(2026年11月時点)。本記事の機能はすべて 8.0 と 8.4 の両方で使えます。
  • WordPress.org の推奨要件は「MySQL 8.0 以上、または MariaDB 10.11 以上」です。一方で、古い環境向けの最低要件として MySQL 5.5.5 も残されています(2026年11月時点)。
  • 配布するプラグインやテーマでは、以下の新機能を前提にしたクエリを書かないでください。利用者の DB が古い MySQL や MariaDB の可能性があります。MariaDB にはウィンドウ関数と CTE はありますが、EXPLAIN ANALYZE や不可視インデックスは構文が異なります。

現在のバージョンは次で確認できます。管理画面の「ツール」→「サイトヘルス」→「情報」の「データベース」でも確認できます。

SELECT VERSION();

5.7 以前から移行する場合の注意点(認証方式・照合順序・sql_mode)は、WordPressをMySQL 8系へ移行するときの変更点と対策にまとめています。

遅いクエリを見つける:Performance Schema と sys スキーマ

どのクエリが DB の時間を使っているかは、Performance Schema の集計で確認できます。MySQL 8 では Performance Schema が既定で有効になっています。なお performance_schema はサーバー起動時にだけ設定できる変数で、SET GLOBAL では切り替えられません。

読みやすい形で見るには sys スキーマのビューを使います。sys.statement_analysis は合計の実行時間が長い順に並んでいます。

-- 合計の実行時間が長いクエリの上位10件(正規化されたクエリ単位)
SELECT query, db, exec_count, total_latency, avg_latency, rows_examined_avg
FROM sys.statement_analysis
WHERE db = 'wordpress'
LIMIT 10;

元の集計表を直接見る場合は、時間の列(SUM_TIMER_WAIT)がピコ秒単位である点に注意してください。

SELECT DIGEST_TEXT,
       COUNT_STAR AS exec_count,
       ROUND(SUM_TIMER_WAIT / 1e12, 2) AS total_sec
FROM performance_schema.events_statements_summary_by_digest
WHERE SCHEMA_NAME = 'wordpress'
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 10;

WordPress では wp_options の自動読み込み、wp_postmeta を使う絞り込み(meta_query)、検索のクエリが上位に来ることが多いです。

実行計画を実測する:EXPLAIN ANALYZE

MySQL 8.0.18 以降の EXPLAIN ANALYZE は、クエリを実際に実行して、各段階の実際の行数と時間を表示します。通常の EXPLAIN は見積もりだけなので、見積もりと実際がずれている箇所を見つけるのに役立ちます。

WordPress のカスタムフィールドで絞り込む WP_Query が発行するのと同じ形のクエリで試す例です。

EXPLAIN ANALYZE
SELECT p.ID
FROM wp_posts AS p
INNER JOIN wp_postmeta AS m ON p.ID = m.post_id
WHERE p.post_type = 'post'
  AND p.post_status = 'publish'
  AND m.meta_key = 'price'
  AND m.meta_value = '1000'
ORDER BY p.post_date DESC
LIMIT 10;

出力の actual time と rows を見て、多くの行を読んでいる段階(Table scan や、rows が大きい Index lookup)を探します。EXPLAIN ANALYZE はクエリを本当に実行するので、重いクエリは本番のピーク時間を避けて試してください。

テーブル接頭辞が wp_ 以外のサイトでは、テーブル名を読み替えてください。

インデックスを安全に試す:不可視インデックス

MySQL 8 では、インデックスを削除せずに「オプティマイザから見えない」状態にできます。使われていないと思われるインデックスを消す前に、不可視にして様子を見られるので、削除による性能劣化を戻しやすくなります。

まず、サーバー起動後に一度も使われていないインデックスを sys スキーマで探します。再起動直後は集計が少ないので、通常の運用を数日続けてから見てください。

SELECT object_schema, object_name, index_name
FROM sys.schema_unused_indexes
WHERE object_schema = 'wordpress';

候補が見つかったら、不可視にして動作と速度を確認します。問題があれば可視に戻すだけで元どおりです。

-- 例:プラグインが追加したインデックスを不可視にする(名前は実際のものに置き換える)
ALTER TABLE wp_example_log ALTER INDEX idx_created INVISIBLE;

-- 状態の確認
SELECT TABLE_NAME, INDEX_NAME, IS_VISIBLE
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = 'wordpress' AND TABLE_NAME = 'wp_example_log';

-- 元に戻す
ALTER TABLE wp_example_log ALTER INDEX idx_created VISIBLE;

WordPress コアのテーブル(wp_posts や wp_postmeta)の標準のインデックスは、コアのクエリが前提にしているので、不可視にしたり削除したりしないでください。追加でインデックスを作る場合も、EXPLAIN ANALYZE で効果を確かめてから本番に入れ、不要なら不可視にして影響を見る、という順で進めると安全です。

集計を1回のクエリで書く:ウィンドウ関数と CTE

MySQL 8.0 から、ウィンドウ関数(ROW_NUMBER() や SUM() OVER など)と共通テーブル式(WITH 句)が使えます。管理用の集計やレポートを、PHP でループを回さずに書けます。

月ごとの公開記事数と、その累計を出す例です。

WITH monthly AS (
  SELECT DATE_FORMAT(post_date, '%Y-%m') AS ym, COUNT(*) AS cnt
  FROM wp_posts
  WHERE post_type = 'post' AND post_status = 'publish'
  GROUP BY ym
)
SELECT ym, cnt, SUM(cnt) OVER (ORDER BY ym) AS running_total
FROM monthly
ORDER BY ym;

投稿者ごとに最新の3記事を取り出す例です。

SELECT post_author, ID, post_title, post_date
FROM (
  SELECT post_author, ID, post_title, post_date,
         ROW_NUMBER() OVER (PARTITION BY post_author ORDER BY post_date DESC) AS rn
  FROM wp_posts
  WHERE post_type = 'post' AND post_status = 'publish'
) AS t
WHERE rn <= 3
ORDER BY post_author, post_date DESC;

自分のサイト専用のコード(テーマの functions.php や自作プラグイン)で使う場合は、テーブル名を $wpdb->posts で組み立て、$wpdb->get_results() で実行します。

<?php
global $wpdb;

$sql = "WITH monthly AS (
    SELECT DATE_FORMAT(post_date, '%Y-%m') AS ym, COUNT(*) AS cnt
    FROM {$wpdb->posts}
    WHERE post_type = 'post' AND post_status = 'publish'
    GROUP BY ym
)
SELECT ym, cnt, SUM(cnt) OVER (ORDER BY ym) AS running_total
FROM monthly
ORDER BY ym";

// 利用者の入力を含まない固定の SQL なので prepare は不要。
// 入力値を含める場合は $wpdb->prepare() を使う。
$rows = $wpdb->get_results( $sql );

$wpdb->prepare() に通す SQL では % がプレースホルダーとして扱われるため、DATE_FORMAT の %Y などは %%Y と書く必要があります。上の例は prepare() を使っていないのでそのままで動きます。

設定を再起動後も残す:SET PERSIST

MySQL 8 の SET PERSIST は、実行中のサーバーの設定を変えると同時に、データディレクトリの mysqld-auto.cnf に書き込み、再起動後も設定を保ちます。SET GLOBAL は再起動で元に戻ります。

WordPress の遅いクエリを調べるために、スロークエリログを有効にする例です。

SET PERSIST slow_query_log = ON;
SET PERSIST long_query_time = 1;

-- ログの出力先の確認
SHOW VARIABLES LIKE 'slow_query_log_file';

-- 永続化した設定の一覧
SELECT * FROM performance_schema.persisted_variables;

-- 永続化をやめる(実行中の値はそのまま)
RESET PERSIST long_query_time;

SET PERSIST には SYSTEM_VARIABLES_ADMIN などの権限が要ります。共用サーバーやマネージド DB では使えないことが多いので、その場合はサービスの管理画面で設定します。

権限をまとめて管理する:ロール

MySQL 8 では権限の組み合わせを「ロール」として作り、ユーザーに割り当てられます。たとえば分析ツールやバックアップ用に、WordPress の DB を読むだけのユーザーを作る場合です。

CREATE ROLE 'wp_readonly';
GRANT SELECT ON wordpress.* TO 'wp_readonly';

CREATE USER 'report'@'localhost' IDENTIFIED BY 'ここに強いパスワード';
GRANT 'wp_readonly' TO 'report'@'localhost';
SET DEFAULT ROLE 'wp_readonly' TO 'report'@'localhost';

WordPress 本体が接続に使うユーザー(wp-config.php の DB_USER)には、テーブルの作成・変更も含めた権限が必要です。読み取り専用のユーザーは WordPress 本体以外の用途に使ってください。

大きなテーブルへの列追加:ALGORITHM=INSTANT

MySQL 8.0.12 以降は、条件を満たす列の追加をテーブルの再構築なしに行えます(8.0.29 以降は末尾以外の位置にも追加できます)。アクセスログなどを貯めるプラグインの大きなテーブルに列を足すときに、ロック時間を短くできます。

-- INSTANT で実行できない変更ならエラーになり、何も変更されない
ALTER TABLE wp_example_log ADD COLUMN source VARCHAR(20) NULL, ALGORITHM=INSTANT;

ALGORITHM=INSTANT を明示しておくと、再構築が必要な変更だった場合にエラーで止まるので、意図せず長時間のロックがかかるのを防げます。コアのテーブルの構造は WordPress のアップデートで管理されるので、独自に列を足すのは自作のテーブルだけにしてください。

使う前の注意

  • 本番で試す前に DB のバックアップを取ります。WP-CLI なら wp db export で書き出せます。
  • general_log(全クエリの記録)は負荷とログの肥大化が大きいので、常時有効にはしません。短時間の調査だけにし、終わったら無効に戻してください。
  • OPTIMIZE TABLE は InnoDB ではテーブルの再構築になり、大きなテーブルでは時間がかかります。定期実行するより、大量削除の後など必要なときに行う方が安全です。
  • sql_mode に NO_AUTO_CREATE_USER を指定する例が古い資料に残っていますが、MySQL 8.0 で廃止されており、指定するとエラーになります。

まとめ

  • WordPress コアは MySQL 8 の新機能を前提にしていないため、恩恵を受けるのは管理者の調査・調整と、自分のサイト専用のコード
  • 遅いクエリは sys.statement_analysis で探し、EXPLAIN ANALYZE で実測する
  • インデックスの削除は、不可視インデックスで影響を確かめてから行う
  • 集計やレポートはウィンドウ関数と CTE で1回のクエリにまとめられる
  • SET PERSIST、ロール、ALGORITHM=INSTANT で設定・権限・列追加の運用が楽になる
  • 移行先は MySQL 8.4 LTS が現実的(2026年11月時点)

お気軽にご相談ください

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