2026年7月22日
2026年7月22日
WordPressの複数データベースを横断検索する方法(SQL・WP-CLI・PHP)
WordPress は通常、1つのサイトにつき1つのデータベースを使います。「複数のデータベースを横断して検索したい」という場面の多くは、次のどれかです。
| 構成 | 横断検索の方法 |
|---|---|
| 同じ MySQL サーバーに、サイトごとのデータベースがある | データベース名.テーブル名 で指定し、UNION ALL でまとめる |
| マルチサイト(1つのデータベースにサイトごとのテーブル) | wp db search --network、または WP-CLI でサイトごとに検索 |
| サイトごとに別のサーバー | WP-CLI のエイリアスで順に検索、または PHP スクリプトで各 DB に接続して結果をまとめる |
1つの WordPress のテーブルを複数のデータベースに分けて置く構成(HyperDB などの db.php ドロップインを使う構成)もありますが、この場合は WordPress の中から見れば普段どおり検索でき、横断のための作業は要りません。なお HyperDB は wordpress.org のプラグインページで、最終更新が2022年10月、動作確認済みのバージョンが WordPress 6.0 系となっています(2026年10月時点)。新しく導入するなら、現在の WordPress での動作をステージングで確かめてください。
前提: 検索用の読み取り専用ユーザーを作る
横断検索には、各データベースの SELECT だけを許可したユーザーを使います。WordPress 本体の DB ユーザー(書き込み権限がある)を使い回さないでください。
CREATE USER 'search_ro'@'localhost' IDENTIFIED BY '十分に長いパスワード';
GRANT SELECT ON site_a.* TO 'search_ro'@'localhost';
GRANT SELECT ON site_b.* TO 'search_ro'@'localhost';
ホスト部分('localhost')は、検索を実行するサーバーから接続するアドレスに合わせます。
同じ MySQL サーバーにある複数のデータベースを検索する
同じ MySQL サーバー上のデータベースなら、テーブルを データベース名.テーブル名 と書けば1つのクエリで横断できます。WordPress の投稿テーブル wp_posts の主キーは ID で、post_id という列はありません(post_id は wp_postmeta などの列です)。
SELECT 'site_a' AS site, ID, post_title, post_date
FROM site_a.wp_posts
WHERE post_status = 'publish'
AND post_type IN ('post', 'page')
AND (post_title LIKE '%キーワード%' OR post_content LIKE '%キーワード%')
UNION ALL
SELECT 'site_b' AS site, ID, post_title, post_date
FROM site_b.wpb_posts
WHERE post_status = 'publish'
AND post_type IN ('post', 'page')
AND (post_title LIKE '%キーワード%' OR post_content LIKE '%キーワード%')
ORDER BY post_date DESC
LIMIT 100;
書くときの注意です。
UNION ALLの前のSELECTの末尾にセミコロンを付けると、そこで文が終わってしまいます。セミコロンは全体の最後に1つだけ付けます。- 接頭辞(
wp_、wpb_など)はサイトごとに違うことがあります。各サイトでwp db prefixを実行して確認します。 - サイトごとに照合順序(collation)が違うと、
Illegal mix of collationsのエラーになることがあります。その場合は列にCOLLATE utf8mb4_unicode_ciのように照合順序を明示して揃えます(指定する照合順序は、列の文字コードに合ったものにします)。 - 下書きやリビジョンも含めたくない場合は、上の例のように
post_statusとpost_typeで絞ります。
マルチサイトを横断検索する
マルチサイトでは、1つのデータベースにサイトごとのテーブル(wp_posts、wp_2_posts、wp_3_posts …)があります。WP-CLI の wp db search に --network を付けると、ネットワーク内の全サイトのテーブルを検索します。データは変更しません。
wp db search 'キーワード' --network --stats
投稿として結果を見たい場合は、サイトの一覧を取ってサイトごとに wp post list を実行します。--s は WordPress の検索(WP_Query の s 引数)と同じ条件で探します。wp post list は既定で投稿(post)が対象なので、固定ページなら --post_type=page を付けます。
wp site list --field=url | while read -r url; do
echo "== $url"
wp post list --url="$url" --s='キーワード' --fields=ID,post_title,url --format=csv
done
別々のサーバーにあるサイトを横断検索する
WP-CLI のエイリアスを使う
各サーバーに SSH で入れて WP-CLI が使えるなら、手元の ~/.wp-cli/config.yml(またはプロジェクトの wp-cli.yml)にエイリアスを定義すると、1つのコマンドで順に実行できます。
@site-a:
ssh: deploy@a.example.com/var/www/html
@site-b:
ssh: deploy@b.example.com/var/www/html
# 定義したすべてのエイリアスで実行する
wp @all post list --s='キーワード' --fields=ID,post_title,url --format=csv
各サイトの WordPress を通して検索するため、接頭辞の違いを意識する必要がありません。
PHP スクリプトで各データベースに接続する
SSH が使えないが DB には接続できる、あるいは結果を1つの表にまとめたい場合は、PHP(CLI)で各データベースに接続し、結果を CSV にまとめます。キーワードはプリペアドステートメントで渡し、SQL に直接埋め込まないようにします。
<?php
// multi-db-search.php: SEARCH_DB_USER=... SEARCH_DB_PASS=... php multi-db-search.php キーワード
if ( PHP_SAPI !== 'cli' ) {
exit( 1 );
}
$keyword = $argv[1] ?? '';
if ( '' === $keyword ) {
fwrite( STDERR, "usage: php multi-db-search.php KEYWORD\n" );
exit( 1 );
}
// 検索対象のサイト(接続先・DB名・接頭辞・表示用URL)
$sites = array(
array( 'host' => 'localhost', 'db' => 'site_a', 'prefix' => 'wp_', 'url' => 'https://a.example.com' ),
array( 'host' => '10.0.0.20', 'db' => 'site_b', 'prefix' => 'wpb_', 'url' => 'https://b.example.com' ),
);
$user = getenv( 'SEARCH_DB_USER' );
$pass = getenv( 'SEARCH_DB_PASS' );
mysqli_report( MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT );
// LIKE の特殊文字(\ % _)をエスケープして部分一致にする
$like = '%' . addcslashes( $keyword, '\\%_' ) . '%';
$results = array();
foreach ( $sites as $site ) {
$table = $site['prefix'] . 'posts';
if ( ! preg_match( '/^[A-Za-z0-9_]+$/', $table ) ) {
continue;
}
try {
$db = new mysqli( $site['host'], $user, $pass, $site['db'] );
$db->set_charset( 'utf8mb4' );
$stmt = $db->prepare(
"SELECT ID, post_title, post_date FROM `{$table}`
WHERE post_status = 'publish' AND post_type IN ('post', 'page')
AND (post_title LIKE ? OR post_content LIKE ?)
ORDER BY post_date DESC LIMIT 50"
);
$stmt->bind_param( 'ss', $like, $like );
$stmt->execute();
foreach ( $stmt->get_result() as $row ) {
$results[] = array( $site['url'], $row['ID'], $row['post_title'], $row['post_date'] );
}
$db->close();
} catch ( mysqli_sql_exception $e ) {
fwrite( STDERR, $site['db'] . ': ' . $e->getMessage() . PHP_EOL );
}
}
// 新しい順に並べて CSV で出力する
usort( $results, fn( $a, $b ) => strcmp( $b[3], $a[3] ) );
$out = fopen( 'php://stdout', 'w' );
fputcsv( $out, array( 'site', 'ID', 'title', 'date' ), ',', '"', '' );
foreach ( $results as $r ) {
fputcsv( $out, $r, ',', '"', '' );
}
fclose( $out );
SEARCH_DB_USER=search_ro SEARCH_DB_PASS='パスワード' php multi-db-search.php 'キーワード' > result.csv
このスクリプトは CLI 専用です。Web から呼び出せる場所(ドキュメントルートの下)には置かないでください。DB の接続情報はスクリプトに書かず、環境変数などで渡します。1つのサイトに接続できなくても、エラーを標準エラー出力に出して残りのサイトの検索を続けます。
検索が遅いとき
LIKE '%キーワード%' のように前後に % が付く検索では、通常のインデックスは使われず、テーブル全体を読みます。post_content は longtext 型なので、そのままでは通常のインデックスも作成できません。投稿数が多いサイトを頻繁に横断検索する場合は、次のように考えます。
- 検索対象を
post_type・post_status・期間で絞り、LIMITを付ける - 定期的に各サイトのデータを1か所に集め、集めたデータに対して検索する
- 全文検索エンジン(Elasticsearch / OpenSearch など)に各サイトの投稿を登録して検索する
WordPress のコアテーブルに独自のインデックスを追加することもできますが、WordPress の更新時の動作やプラグインとの兼ね合いを確かめる必要があるため、まずステージングで効果と影響を確認してください。
まとめ
- 同じ MySQL サーバーなら
データベース名.テーブル名とUNION ALLで1つのクエリにできる。wp_postsの主キーはID - マルチサイトは
wp db search --networkや、wp site listとwp post list --sの組み合わせで横断する - 別サーバーなら WP-CLI の
@allエイリアスか、PHP スクリプトで各 DB に接続して結果をまとめる - 検索には
SELECTだけを許可したユーザーを使い、キーワードはプリペアドステートメントで渡す LIKE '%…%'は通常のインデックスが効かないので、対象を絞るか検索の仕組みを別に用意する