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 '%…%' は通常のインデックスが効かないので、対象を絞るか検索の仕組みを別に用意する

お気軽にご相談ください

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