本番データの移行は、一度実行すると取り返しがつかないことが多く、非常にリスクが高い作業です。
「php artisan migrate --pretend」で安全に確認できると思っていませんか?
実は --pretend
はテーブル構造変更しか表示されず、データ移行系では役に立ちません。
この記事では Laravelで本番データ移行を安全に行うための実装パターン を紹介します。
Laravel Migration の --pretend
の落とし穴
Laravelには php artisan migrate --pretend
というオプションがあり、実際にSQLを実行せずに「どんなSQLが発行されるか」を確認できます。
php artisan migrate --pretend
ただし、この方法には大きな制限があります。
- Schemaビルダーによるテーブル変更は表示される
- クエリビルダやDBファサードで書かれた
insert, update, delete
は表示されない
例えば以下のような「データ移行系のMigration」ではSQLが出力されません👇
public function up()
{
$users = DB::table('users')->where('status', 'inactive')->get();
foreach ($users as $user) {
DB::table('archived_users')->insert([
'id' => $user->id,
'name' => $user->name,
]);
}
}
--pretend
をつけても何も表示されないので、実際にどんなデータが更新されるか事前確認できない という問題があるわけです。
そこで必要なのが「Dry Run実装」
本番実行の前に「実際には更新せず、対象件数や対象データを確認できる」仕組みを入れておくことが重要です。
以下では Laravel を例に、Dry Run実装のパターンを共有します。
1. Dry Run の実装方法
(1) トランザクション + ロールバック
DB::beginTransaction();
try {
$targets = User::where('status', 'inactive')->get();
echo "対象件数: " . $targets->count() . PHP_EOL;
echo "対象ID: " . implode(',', $targets->pluck('id')->toArray()) . PHP_EOL;
// 実際の更新(Dry Runではコメントアウト)
// User::where('status', 'inactive')->update(['status' => 'archived']);
DB::rollBack(); // Dry Runなので反映しない
} catch (\Exception $e) {
DB::rollBack();
throw $e;
}
Dry Run では最後に必ず
DB::rollBack()
を呼び出して変更を反映しないようにします。
本番処理と混在させると、ロールバックを入れ忘れて本当に更新してしまう危険があるため、Dry Run 用のコードとして切り分けて書くのが安全です。
(2) DB::listen()
でSQLログを出力
DB::listen()
を使うと、実際に発行されるSQLをリアルタイムで確認できます。
Dry Runの確認用に使うと便利です。
DB::listen(function ($query) {
// 発行されたSQLをそのまま表示
echo $query->sql . PHP_EOL;
});
例えば、対象ユーザーを取得するクエリの場合はこんな出力がされます。
select * from `users` where `status` = 'inactive'
これにより、「このDry Runでどのレコードが対象になるのか」を事前に確認できます。
本番処理を走らせる前に、発行されるSQLをチェックして安全性を確かめましょう。
(3) 独自 --dry-run オプションを作る
Laravel の Artisan コマンドでは、自分でオプションを定義して切り替えることができます。
例えば --dry-run をつけたら「実際には更新せず、対象データだけ表示する」といった動作にできます。
public function handle()
{
$dryRun = $this->option('dry-run');
$targets = User::where('status', 'inactive');
if ($dryRun) {
$this->info('対象件数: ' . $targets->count());
$this->info('対象ID: ' . implode(',', $targets->pluck('id')->toArray()));
} else {
$targets->update(['status' => 'archived']);
}
}
ポイント
- --dry-run オプションをつけると「確認モード」として動作する
php artisan user:archive --dry-run
➝ 対象件数や対象IDだけが表示される
- オプションを外すと「本番モード」として実際の更新が走る
php artisan user:archive
- 本番処理とDry Run処理を一つのコマンドにまとめつつ、安全に切り替えられる
👉 つまり、この方法を使えば「Dry Runコードと本番コードが同居しても、明示的に --dry-run
をつけない限り更新されない」仕組みになるので安心です。
2. バックアップ・リストアの仕組み
mysqldump
でバックアップ
mysqldump -u user -p database_name target_table > target_table_backup.sql
3. 本番環境での安全性を高める工夫
- 件数チェック
$expected = 500;
$actual = User::where('status', 'inactive')->count();
if ($expected !== $actual) {
throw new \Exception("件数不一致: expected=$expected, actual=$actual");
}
- 分割処理(チャンク更新)
User::where('status', 'inactive')
->chunkById(1000, function ($users) {
foreach ($users as $user) {
$user->update(['status' => 'archived']);
}
});
まとめ:安全な本番移行フロー
✅ Dry Runを実行して対象件数・対象データを確認
✅ バックアップを取得
✅ 件数チェックで期待値と一致しているか検証
✅ 大量データはチャンク更新
✅ 実行前にチームレビュー