第5回: DB事故と移転。シリアライズデータと整合性の防衛
第4回では、Reactエラーやヘッドレス構成で「画面が動かない」原因を追いました。最後に残る問いはひとつです。その状態がDBに永続化された瞬間、何が起きるか。
DB移転でURLを置換したら管理画面が崩壊する。侵入・改ざんがDBに残り続ける。これが「戻れないDB事故」です。
第5回は「DB事故を戻す・防ぐ回」です。
導入:DB事故は「戻れない」を作る
だから先に戻れる状態を作る
- シリアライズを壊さずに移転・置換できる
- 巨大DBでもタイムアウトさせずにimportできる(分割/CLI)
- wp search-replace を事故らせない型で運用できる
- 文字化け/照合順序不整合を症状から診断し統一できる
- AUTO_INCREMENT/重複キーを原因から潰して復旧できる
- 管理画面に入れない時でも緊急ユーザーを安全に作れる
DB事故の分類(今回扱う領域)
- 移転(ドメイン/パス変更)
- 置換(search/replace、特にシリアライズ)
- 文字化け(charset/collation混在)
- キー崩壊(AUTO_INCREMENT/重複キー)
- ログイン不能(権限/URL/プラグイン/WAF/DB破損)
まず環境を確定する(ここで手順が分岐する)
以降の手順は
「推奨フロー(SSH/WP-CLIあり)」
↓
「代替フロー(SSH不可・phpMyAdmin中心)」
の順で書きます。
環境が不明な場合は、まず以下で確定してください。
- DB種別(MySQL/MariaDB)を確定
- SSH/WP-CLI可否を確定(推奨はCLI)
- マルチサイトか判定
- レンタル/ VPS など制約を把握
✅確認コマンド(SSH/WP-CLIがある場合)
目的:環境判定(DB種別/マルチサイト/DBサイズ)
前提:WP-CLI可(不可ならphpMyAdminで同等SQL)
実行:
wp db query "SELECT VERSION();"
wp db query "SHOW VARIABLES LIKE 'version_comment';" # MariaDB判定のヒント
wp db query "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name LIKE '%_blogs';"
wp db sizeBash確認:推奨フロー(CLI)/代替フロー(SSH不可)を選ぶ
戻し方:なし
実行:
✅SSHが無い場合の確認(phpMyAdmin 等)
- DB種別/バージョン:
phpMyAdminの「データベースサーバ」表示、または「変数」(version) を確認 - マルチサイト判定:
wp-config.php に MULTISITE 定数があるか、または wp_blogs テーブルが存在するかを確認(接頭辞は環境で異なる)
安全の最低要件:危険操作の前に「復元可能」を作る
本番DBの作業は、復元テストまでが作業です。最低要件を満たせないなら、作業を止める判断が正解になります。
- DBフルバックアップ(dump)を取得し、復元テスト(最低でもテーブル数/件数確認)を行う
- 可能ならホスティングのスナップショット(サーバ機能)も併用する
- 作業前後で「URL」「テーブル接頭辞」「対象環境(本番/検証)」を明記し、取り違えを防ぐ
作業を止める判断基準(やらない根拠)
- バックアップ取得または復元テストができない
- 本番/検証や接頭辞が識別できない
- 侵入疑いが強いのに隔離/調査せず作業する
DB移転で壊れる「本当の理由」
移転=参照整合性の再構築
DB移転を「SQLを持っていく作業」と捉えると事故ります。
実態は、参照整合性(参照が正しく繋がっている状態)の再構築です。WordPressはDBに「データ」だけでなく「設定」「状態」「構造」も保存します。
wp_options:home/siteurl、ウィジェット設定、プラグイン設定、キャッシュキー、サイト状態フラグwp_postmeta:
ページビルダーやブロックの属性、ACFなどの構造化メタwp_posts:
本文中のURL、ブロックコメント内の属性、ショートコードの引数- マルチサイト:
wp_blogs/wp_sitemeta、サイトごとのURLとパスの二重管理
移転時の定義(今回の前提)
- 移転=「URL(ドメイン)」「ファイルパス」「環境差分(HTTPS/HTTP、サブディレクトリ、CDN)」を揃え、DB内の参照を壊さずに繋ぎ直す作業
- 置換=「繋ぎ直しの手段」。ただし無差別置換は「構造破壊」になり得る
このあと扱う「シリアライズ事故」は、参照整合性を直すつもりで、構造そのものを壊してしまう典型です。
シリアライズ事故:壊れ方→検出→安全策
シリアライズデータ=PHPのserialize()形式で配列/オブジェクトを文字列化したもの(文字列長が埋め込まれるため、置換で長さがズレると復元できなくなる)。
- 管理画面のウィジェットが消える/配置がリセットされる
- プラグイン設定画面が真っ白、設定保存が効かない
- ログに
unserialize()系の警告が出る(PHP error log に残りやすい) - 移転後に「表示は出るが一部機能だけ死ぬ」=DBの設定値だけ壊れているサイン
壊れる理由(検索置換が危険な場面)
シリアライズ文字列は s:12:"example"; のように、s: の直後に文字列長が入ります。ここでドメインやパスを置換すると長さが変わり、復元時にズレて破損します。
- phpMyAdminの「検索置換」や、SQLの
UPDATE ... SET col = REPLACE(col, ...)を、wp_options/wp_postmetaなどに無差別に当てる - 本番DBに対して、対象テーブル/列を絞らずに置換を走らせる
- マルチサイトで、
--network相当の扱いを理解せずにサイトURLだけを置換する
検出アプローチ(壊れている/壊れそうを先に掴む)
検出は「ログ」→「データ」→「アプリ挙動」の順で、コストの低いものから当てます。
1️⃣ まずログを確認します。(第1回の型を流用)
unserialize系を拾う ※環境によりログパスは異なるので、まず場所を確定する
grep -R "unserialize" -n /path/to/logs | headBash2️⃣ DB側:
wp_optionsの中でserializeっぽい値をざっくり抽出する(厳密な判定ではないことに留意)もしWP-CLIが使えるなら「読み取り専用」で安全に棚卸しできる
wp db query "SELECT option_name FROM wp_options WHERE option_value LIKE 'a:%' OR option_value LIKE 'O:%' OR option_value LIKE 's:%' LIMIT 50;"Bash3️⃣置換前の棚卸し:
置換対象がどのテーブル/列に多いか(dry-runで見るのが一番安全)
→ 次セクションで型として固定
推奨の安全策(結論)
置換が必要なら、原則 wp search-replace を使い、dry-run→本番→検証→必要なら即ロールバックの順で固定します(WP-CLIのsearch-replaceはPHPのシリアライズデータを扱える旨が公式に明記されています)。
実際の更新を行わず、変更件数と対象範囲だけを確認する実行処理。
- dry-run時点で置換件数が想定より桁違いに多い(スコープ設計ミス)
- 実行後にログへunserialize系が出始めた/管理画面の設定が飛んだ
- マルチサイトで想定外のサイトまで影響している(network扱いの理解不足)
WP-CLIでの安全な search-replace(型)
ここは「型」を覚えるだけで事故率が落ちます。WP-CLIの wp search-replace は、DB内の文字列を置換しつつ、PHPシリアライズデータにも対応すると公式に明記されています。
DB内の文字列を置換しつつ、PHPシリアライズデータにも対応
実行前チェックリスト(事故の8割はここで防げる)
- 対象環境を明示:URL / テーブル接頭辞 / 本番 or 検証
- dump取得+復元テスト(最低:テーブル数と件数の照合)
- マルチサイト判定(trueなら
--networkを検討) - 置換対象の棚卸し(dry-runで件数・対象テーブルを把握)
- 除外設計:
guid(投稿の恒久識別子。原則置換しない)などを決める
目的→前提→実行→確認→戻し方
以下はそのまま使えるテンプレです。ドメイン移転例(old→new)。
DB内の参照URLを新ドメインへ安全に置換し、シリアライズ破損を避ける
SSH/WP-CLIが使える。バックアップ済み。作業対象のDB接頭辞を把握している
実行1️⃣ dry-run:まず件数と対象を見て、想定外が無いか確認
wp search-replace 'http://old.example' 'https://new.example' \
--dry-run \
--all-tables-with-prefix \
--skip-columns=guidBash実行2️⃣ 本番:dry-runでOKだった場合のみ実行
wp search-replace 'http://old.example' 'https://new.example' \
--all-tables-with-prefix \
--skip-columns=guidBash実行3️⃣ マルチサイトの場合:ネットワーク全体に適用する(必要な時だけ)
wp search-replace 'http://old.example' 'https://new.example' --network --all-tables-with-prefix --skip-columns=guidBash確認:
置換直後に「ログ」「管理画面の主要画面」「代表ページの表示」「ログイン/保存系」を最短で確認します。
- エラーログにunserialize系が増えていない
- ログイン→プロフィール保存→ウィジェット画面→メディア一覧(崩壊しやすい箇所)
- リンク切れ(画像/JS/CSS)が増えていない(DevToolsのNetworkで 404 を見る)
戻し方:
異常が出たら、置換で直そうとせず「dumpで戻す」が最短です。
例:事前に取得した dump.sql.gz へ戻す(詳細は次セクション)
※作業対象が本番DBであることを再確認してから
gunzip -c dump.sql.gz | wp db import -Bash危険例 vs 安全策(最小の対比)
- –dry-run で棚卸し → OKなら本番
- 対象テーブルはWP接頭辞に限定(–all-tables-with-prefix)
- guidは原則除外(–skip-columns=guid)
巨大DBの搬送:タイムアウトを設計で潰す
phpMyAdminで巨大SQLを投げてタイムアウト、は「仕様」です。ギガバイト級はGUI前提を捨てて、dump→圧縮→転送→CLIインポートに寄せます。
失敗パターン(先に潰す)
- phpMyAdminのアップロード上限/実行時間に引っかかる
- ブラウザ経由で途中切断
→ 半端に入って「重複キー」や「文字化け」へ連鎖 - 巨大1ファイルで再開不能(どこで死んだか不明)
推奨フロー(SSH/WP-CLIあり)
目的:巨大DBをタイムアウトさせずに搬送/importする
前提:SSH/WP-CLI可。作業前dumpは別保管
実行:
wp db export dump.sql
gzip -9 dump.sqlBash(転送)scp/rsync 等で dump.sql.gz を移動
gunzip -c dump.sql.gz | wp db import -Bash確認:
wp db query "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE();"
wp db query "SELECT COUNT(*) FROM wp_posts;"Bash戻し方:before_dump.sql.gz をimport
分割・ストリーム・再開可能性(巨大向けの追加手当)
再開不能が怖いなら、分割して「途中からやり直せる」形にします。
目的:分割して再開可能にする
前提:dump.sql がある
実行:
split -b 200m dump.sql dump.part.
for f in dump.part.*; do wp db import "$f" || break; doneBash確認:失敗したパート以降だけ再実行できる
戻し方:事前dumpをimport
代替フロー(SSH不可・phpMyAdmin中心)
SSHが無い場合は「DBの持ち出し」自体が制約になります。現実解は2つ。
- ホスティング側の「バックアップ/復元」機能
(DB単体のエクスポートが可能ならそれを使う) - 移行専用ツールのDB搬送機能を使う
(ただし置換・シリアライズ破損リスクは別途評価)
いずれの場合も、import後に必ず「照合順序」「AUTO_INCREMENT」「ログイン」を点検して、次の事故を未然に止めます。
文字化け/照合順序(collation)事故の対応
charset=文字コード体系(例:utf8mb4)
collation=その文字コードでの比較/並び順ルール(例:utf8mb4_unicode_ci)
症状別診断例
1️⃣ 文字コードを確認します
SHOW VARIABLES LIKE 'character_set%';Bash2️⃣ wp-config.php の DB_CHARSET/DB_COLLATE を確認します
1️⃣ information_schema で混在箇所を特定する
DBやテーブル等に関するメタデータを格納しているデータベース。データの絞り込みができるようになる。
2️⃣ 影響範囲を絞って統一(無差別CONVERTは禁止)
サーバの対応collationを確認し、サイト全体の方針を決めて統一
現状把握(読み取り専用で混在を可視化)
目的:混在している「場所」と「範囲」を特定し、変換対象を最小化する
前提:WP-CLIが使えるなら wp db query が安全(接続情報を再利用できる)
実行:
wp db query "SHOW VARIABLES LIKE 'character_set%';"
wp db query "SHOW VARIABLES LIKE 'collation%';"
wp db query "SELECT TABLE_NAME, TABLE_COLLATION FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE();"
wp db query "SELECT TABLE_NAME, COLUMN_NAME, CHARACTER_SET_NAME, COLLATION_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME IN ('wp_posts','wp_postmeta','wp_options');"Bash確認:混在箇所を絞る
戻し方:なし
対処フロー(統一→再発防止)
- 範囲確定:
混在が「特定テーブルだけ」か「DB全体」かを決める - 統一方針:
MySQL 8なら utf8mb4 + utf8mb4_0900_ai_ci が使えることが多い。MariaDB等では utf8mb4_unicode_ci 系が現実的(未確認なら現行に寄せる) - 変換:
テーブル単位でALTER TABLE ... CONVERT TO CHARACTER SET ...を適用(公式ドキュメントで構文確認) - 再発防止:
wp-config.php の DB_CHARSET/DB_COLLATE を統一方針に合わせる(DB側とズレるなら修正)
- MySQL ALTER TABLE
- MySQL Character Sets and Collations
- MariaDB ALTER TABLE
- MariaDB Character Sets and Collations
禁止条件(無差別変換の危険)
- バックアップが無い/復元テストができない
- 巨大テーブルでロック時間が許容できない(本番ピークに当てない)
- MySQL/MariaDBのバージョン差で目的のcollationが存在しない(存在確認前に実行しない)
AUTO_INCREMENT/重複キー:発火点→修復→検証
AUTO_INCREMENT=INSERT時に自動採番する仕組み(主にID列)
これが巻き戻ると、次のINSERTで既存IDと衝突して「Duplicate entry」が出ます。
典型発火点(原因)
- 途中まで入ったimportを再実行して二重投入(再開不能運用)
- テーブルを部分削除したのにAUTO_INCREMENTが更新されていない
- プラグイン/バッチが明示IDでINSERTしている(ID衝突)
- 移転でテーブル構造だけ作ってデータ投入順が崩れ、参照整合が壊れる
修復フロー
目的:
衝突しているキーを特定し、AUTO_INCREMENTを正常値へ戻す。重複データがあるなら「どれを正とするか」を決めてから削除する。
前提:
作業前にdump取得(このセクションのSQLは破壊的になり得るため)。
実行例:wp_posts
MAX_ID=$(wp db query "SELECT MAX(ID) FROM wp_posts;" --skip-column-names)
wp db query "ALTER TABLE wp_posts AUTO_INCREMENT=$((MAX_ID+1));"
wp db query "SHOW INDEX FROM wp_posts;"Bash確認:保存/新規作成が通る
戻し方:事前dumpをimport
- MySQL ALTER TABLE(AUTO_INCREMENT指定を含む)
- MariaDB ALTER TABLE
重複データの扱い(消す前に退避)
消すなら必ず退避してから。
理想は検証環境で再現→削除手順を確定→本番へ適用です。
例:重複候補を退避テーブルへコピー(テーブル/条件は必ず環境に合わせる)
CREATE TABLE wp_postmeta_dupe LIKE wp_postmeta;
INSERT INTO wp_postmeta_dupe SELECT * FROM wp_postmeta WHERE ...;Bash例:重複値の抽出(col は実際のユニーク制約列に合わせる)
SELECT col, COUNT(*) c FROM your_table GROUP BY col HAVING c > 1;Bash検証(直ったかの確認)
- 同じ操作(import/更新/投稿保存)を再実行してエラーが再発しない
- 主要テーブルのMAX(ID)とAUTO_INCREMENTの関係が破綻していない
- アプリ側で「保存できるか」「新規投稿できるか」「ユーザー追加できるか」を最短で確認
上級者Q&A:管理画面に入れない(緊急ユーザー作成)
ログイン不能は「DB事故」だけが原因ではありません。最終手段に行く前に、切り分けを1周だけ回します。
まずの切り分け
1) wp-login.php は開ける?
├─ No → WAF/Basic認証/メンテ/ルーティング(URL・HTTPS)を先に解決
└─ Yes → 2へ
2) パスワード再発行メールは届く?
├─ Yes → まず正攻法で復旧(DB直叩き不要)
└─ No → 3へ
3) 既存ユーザーは存在する?(DBでwp_usersを確認)
├─ Yes → 権限/ロール破損、ユーザーメタ欠損の可能性
└─ No → import失敗/テーブル欠損の可能性(先にDB整合性を確認)
最終手段:緊急ユーザー作成(WP-CLI優先、次にSQL)
最終手段A:WP-CLIで作る(推奨)
目的:緊急ログイン口を作り、原因調査と復旧を進める
前提:SSH/WP-CLIが使える。作業後に削除/権限縮小する(作りっぱなし禁止)。
実行:緊急ユーザー作成(最小限)
wp user create emergency_admin emergency@example.com --role=administrator --user_pass='ChangeMe_UseRandom'Bash確認:ユーザーが作成されたか?
wp user get emergency_admin --field=IDBash戻し方(後始末):復旧後に削除(または権限を落とす)
wp user delete emergency_admin --yesBash最終手段B:SQLで作る(SSH/WP-CLI不可の最終手段)
前提条件:
- テーブル接頭辞(例:
wp_)を特定できること - 管理者権限のDB操作が可能であること
- バックアップ済みであること
注意:
WordPressのパスワードは通常は強固なハッシュですが、後方互換のため古いMD5ハッシュを扱う経路が存在します(公式:check_password)。この手段が通るかは環境依存なので、成功/失敗を必ず検証し、ダメなら別ルート(ホスティングサポート/ファイル改修)へ切り替えます。
目的→前提→実行→確認→戻し方(SQL版テンプレ)
目的:
緊急ユーザーを作り、最短でログインできる状態を作る
前提:
接頭辞 wp_ は例。必ず自環境の接頭辞に置換すること
実行:
ユーザー作成(user_pass に MD5 を入れる例。環境により通らない可能性あり)
VALUES ('emergency_admin', MD5('ChangeMe_UseRandom'), 'emergency_admin', 'emergency@example.com', NOW(), 0, 'emergency_admin');
INSERT INTO wp_users (user_login, user_pass, user_nicename, user_email, user_registered, user_status, display_name)Bash実行:
権限付与(単一サイト:administrator)
meta_key の接頭辞も合わせる(例:wp_capabilities / wp_user_level)
SET @uid := (SELECT ID FROM wp_users WHERE user_login = 'emergency_admin' LIMIT 1);
INSERT INTO wp_usermeta (user_id, meta_key, meta_value)
VALUES
(@uid, 'wp_capabilities', 'a:1:{s:13:"administrator";b:1;}'),
(@uid, 'wp_user_level', '10');Bash確認:
ログイン→パスワード変更→権限表示
戻し方:
復旧後に該当ユーザー/メタを削除(作りっぱなし禁止)
戻し方(後始末):
復旧が終わったら、緊急ユーザーは必ず削除。残すなら権限を最小化し、監査ログ(いつ/誰が作ったか)を記録します。
- パスワードは即変更(長くランダム)+可能なら2FA
- 緊急ユーザー削除 or 権限縮小(administratorのまま放置しない)
- 侵入疑いがある場合:このユーザーでログインできた=原因解決ではない(第6回でフォレンジックとして取り上げます)
サイト内の「管理者」とネットワーク全体の「スーパー管理者」は別です。緊急ユーザーでNetwork Adminに入れない場合は、既存スーパー管理者の有無や wp_sitemeta を確認してください。
まとめ
- 移転は「参照整合性の再構築」。置換は手段であり、無差別置換は構造破壊になる
- シリアライズは文字列長が埋め込まれるため、SQLのREPLACEで壊れやすい。置換は
wp search-replaceの型でやる - 巨大DBはGUI前提を捨て、dump→圧縮→転送→CLIインポート。再開不能を嫌うなら分割する
- 文字化け/照合順序は症状から「接続」か「混在」かを切り分け、範囲を絞って統一する
- AUTO_INCREMENT/重複キーは「値」「キー」「原因」を読んでから直す。消すなら退避して検証で再現
- ログイン不能の最終手段は緊急ユーザー作成。WP-CLI優先、SQLは条件付きで。後始末までが作業
明日からの実務アクション(チェックリスト)
✅移転/置換の標準手順
⬜︎ 作業対象(本番/検証)証)・URL・接頭辞を紙に書く(取り違え防止)
⬜︎ dump取得 → 復元テスト(テーブル数/件数)
⬜︎ wp search-replace は必ず --dry-run → スコープ調整 → 本番
⬜︎ 実行後は「ログ」「保存系」「主要画面」の最短確認
⬜︎ 異常が出たら置換で追いかけず dump で戻す(最短復旧)
✅巨大DBの標準手順
⬜︎ export → gzip → transfer → import(STDIN推奨)
⬜︎ 再開不能が怖いなら split してパートごとにimport
✅文字化け/照合順序
⬜︎ SHOW VARIABLES / information_schema で混在を可視化
⬜︎ 変換は範囲を絞って、バックアップ前提で実行
保険設計(事故を起こさない仕組み)
- 検証環境を標準化し、本番は最終適用だけ
- 置換コマンドをテンプレ化(dry-run/除外/対象)
- バックアップを二重化(dump+スナップショット)
参考リンク一覧
- WP-CLI: wp search-replace https://developer.wordpress.org/cli/commands/search-replace/
- WP-CLI: wp db export https://developer.wordpress.org/cli/commands/db/export/
- WP-CLI: wp db import https://developer.wordpress.org/cli/commands/db/import/
- WP-CLI: wp user create https://developer.wordpress.org/cli/commands/user/create/
- WP-CLI: wp user delete https://developer.wordpress.org/cli/commands/user/delete/
- WordPress Developer Resources: check_password https://developer.wordpress.org/reference/hooks/check_password/
- MySQL: Character Sets and Collations https://dev.mysql.com/doc/refman/8.0/en/charset.html
- MySQL: ALTER TABLE https://dev.mysql.com/doc/refman/8.0/en/alter-table.html
- MariaDB: Character Sets and Collations https://mariadb.com/kb/en/character-sets-and-collations/
- MariaDB: ALTER TABLE https://mariadb.com/kb/en/alter-table/
「戻れない事故」を扱います。
マルウェア感染によるデータ改ざん、DB移転時のシリアライズ破壊など、JSの失敗や外部攻撃が「データベースへ永続化される瞬間」とその救出策について解説します 。


