上級者向け

第5回: DB事故と移転。シリアライズデータと整合性の防衛

第5回: DB事故と移転。シリアライズデータと整合性の防衛
WEB先案内

第4回では、Reactエラーやヘッドレス構成で「画面が動かない」原因を追いました。最後に残る問いはひとつです。その状態がDBに永続化された瞬間、何が起きるか

DB移転でURLを置換したら管理画面が崩壊する。侵入・改ざんがDBに残り続ける。これが「戻れないDB事故」です。

第5回は「DB事故を戻す・防ぐ回」です。

目次
  1. 導入:DB事故は「戻れない」を作る
    だから先に戻れる状態を作る
  2. DB移転で壊れる「本当の理由」
    移転=参照整合性の再構築
  3. シリアライズ事故:壊れ方→検出→安全策
  4. WP-CLIでの安全な search-replace(型)
  5. 巨大DBの搬送:タイムアウトを設計で潰す
  6. 文字化け/照合順序(collation)事故の対応
  7. AUTO_INCREMENT/重複キー:発火点→修復→検証
  8. 上級者Q&A:管理画面に入れない(緊急ユーザー作成)
  9. まとめ
  10. ウェブナラはじめました!

導入: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 size
Bash

確認:推奨フロー(CLI)/代替フロー(SSH不可)を選ぶ
戻し方:なし
実行:

✅SSHが無い場合の確認(phpMyAdmin 等)

  • DB種別/バージョン:
    phpMyAdminの「データベースサーバ」表示、または「変数」(version) を確認
  • マルチサイト判定:
    wp-config.php に MULTISITE 定数があるか、または wp_blogs テーブルが存在するかを確認(接頭辞は環境で異なる)

安全の最低要件:危険操作の前に「復元可能」を作る

本番DBの作業は、復元テストまでが作業です。最低要件を満たせないなら、作業を止める判断が正解になります。

  • DBフルバックアップ(dump)を取得し、復元テスト(最低でもテーブル数/件数確認)を行う
  • 可能ならホスティングのスナップショット(サーバ機能)も併用する
  • 作業前後で「URL」「テーブル接頭辞」「対象環境(本番/検証)」を明記し、取り違えを防ぐ

作業を止める判断基準(やらない根拠)

  • バックアップ取得または復元テストができない
  • 本番/検証や接頭辞が識別できない
  • 侵入疑いが強いのに隔離/調査せず作業する

DB移転で壊れる「本当の理由」
移転=参照整合性の再構築

DB移転を「SQLを持っていく作業」と捉えると事故ります。

実態は、参照整合性(参照が正しく繋がっている状態)の再構築です。WordPressはDBに「データ」だけでなく「設定」「状態」「構造」も保存します。

壊れやすい代表ポイント(どこが壊れるか)

移転時の定義(今回の前提)

  • 移転=「URL(ドメイン)」「ファイルパス」「環境差分(HTTPS/HTTP、サブディレクトリ、CDN)」を揃え、DB内の参照を壊さずに繋ぎ直す作業
  • 置換=「繋ぎ直しの手段」。ただし無差別置換は「構造破壊」になり得る

このあと扱う「シリアライズ事故」は、参照整合性を直すつもりで、構造そのものを壊してしまう典型です。

シリアライズ事故:壊れ方→検出→安全策

シリアライズデータ=PHPのserialize()形式で配列/オブジェクトを文字列化したもの(文字列長が埋め込まれるため、置換で長さがズレると復元できなくなる)。

典型症状(現場での見え方)

壊れる理由(検索置換が危険な場面)

シリアライズ文字列は s:12:"example"; のように、s: の直後に文字列長が入ります。ここでドメインやパスを置換すると長さが変わり、復元時にズレて破損します。

禁止例(やると高確率で壊れる)

検出アプローチ(壊れている/壊れそうを先に掴む)

検出は「ログ」→「データ」→「アプリ挙動」の順で、コストの低いものから当てます。

1️⃣ まずログを確認します。(第1回の型を流用
unserialize系を拾う ※環境によりログパスは異なるので、まず場所を確定する

grep -R "unserialize" -n /path/to/logs | head
Bash

2️⃣ 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;"
Bash

3️⃣置換前の棚卸し:
置換対象がどのテーブル/列に多いか(dry-runで見るのが一番安全)
→ 次セクションで型として固定

推奨の安全策(結論)

置換が必要なら、原則 wp search-replace を使い、dry-run→本番→検証→必要なら即ロールバックの順で固定します(WP-CLIのsearch-replaceはPHPのシリアライズデータを扱える旨が公式に明記されています)。

dry-runとは?
ロールバック判断ライン(戻すか進めるか)

WP-CLIでの安全な search-replace(型)

ここは「型」を覚えるだけで事故率が落ちます。WP-CLIの wp search-replace は、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=guid
Bash

実行2️⃣ 本番:dry-runでOKだった場合のみ実行

wp search-replace 'http://old.example' 'https://new.example' \
  --all-tables-with-prefix \
  --skip-columns=guid
Bash

実行3️⃣ マルチサイトの場合:ネットワーク全体に適用する(必要な時だけ)

wp search-replace 'http://old.example' 'https://new.example' --network --all-tables-with-prefix --skip-columns=guid
Bash

確認:
置換直後に「ログ」「管理画面の主要画面」「代表ページの表示」「ログイン/保存系」を最短で確認します。

  • エラーログに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.sql
Bash

(転送)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; done
Bash

確認:失敗したパート以降だけ再実行できる
戻し方:事前dumpをimport

代替フロー(SSH不可・phpMyAdmin中心)

SSHが無い場合は「DBの持ち出し」自体が制約になります。現実解は2つ。

  • ホスティング側の「バックアップ/復元」機能
    (DB単体のエクスポートが可能ならそれを使う)
  • 移行専用ツールのDB搬送機能を使う
    (ただし置換・シリアライズ破損リスクは別途評価)

いずれの場合も、import後に必ず「照合順序」「AUTO_INCREMENT」「ログイン」を点検して、次の事故を未然に止めます。

文字化け/照合順序(collation)事故の対応

charset=文字コード体系(例:utf8mb4)
collation=その文字コードでの比較/並び順ルール(例:utf8mb4_unicode_ci)

症状別診断例

症状A:画面の日本語が「????」や「é」になる
SHOW VARIABLES LIKE 'character_set%';
Bash
症状B:SQLエラーに “Illegal mix of collations” が出る
information_schemaとは?
症状B:一部の検索/ソートだけおかしい(濁点/絵文字/全角半角)

現状把握(読み取り専用で混在を可視化)

目的:混在している「場所」と「範囲」を特定し、変換対象を最小化する
前提: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/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

公式の確認先

重複データの扱い(消す前に退避)

消すなら必ず退避してから。
理想は検証環境で再現→削除手順を確定→本番へ適用です。
例:重複候補を退避テーブルへコピー(テーブル/条件は必ず環境に合わせる)

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=ID
Bash

戻し方(後始末):復旧後に削除(または権限を落とす)

wp user delete emergency_admin --yes
Bash

最終手段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回でフォレンジックとして取り上げます)
補足(マルチサイト)

まとめ

要点
  • 移転は「参照整合性の再構築」。置換は手段であり、無差別置換は構造破壊になる
  • シリアライズは文字列長が埋め込まれるため、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+スナップショット)

参考リンク一覧

次回、第6回では

「戻れない事故」を扱います。

マルウェア感染によるデータ改ざん、DB移転時のシリアライズ破壊など、JSの失敗や外部攻撃が「データベースへ永続化される瞬間」とその救出策について解説します 。

ココナラよりも安い!

ウェブナラはじめました!

ABOUT ME
WEBさん
WEBさん
WordPressの不具合をなおす人
あなたのお仕事をする時間を使ってITに関することを調べたり、トライしてみたりして、それでもうまくいかない。そんなことはありませんか? WEB先案内をご利用いただくと、困ったときにITの顧問としてあなたのITに関するお悩みにお答えし、サポートを行うことができます。
記事URLをコピーしました