先に要点
- DBのdry run(ドライラン)は、実際には変更せず「実行したら何が起きるか」だけを確認すること。本番のUPDATE・DELETE事故の多くは、実行前の数分で防げます。
- いちばん基本で、いちばん効くのは同じWHERE句でSELECTして件数を数えること。「10件のはずが8万件」にその場で気づけます。
- BEGIN → 実行 → ROLLBACK も有効。ただしMySQLのDDL(ALTER TABLE等)は暗黙のコミットが起きて戻せません。DMLとDDLで話がまったく変わります。
- dry runは万能ではない。件数が合っていてもロック・トリガー・実行時間までは分かりません。最後の砦は戻せるバックアップです。
本番でUPDATEを流したら、想定の1万倍の行が書き換わった ── データベースの事故は、だいたいこの形をしています。厄介なのはSQL自体は文法的に正しかったことです。WHERE句の条件が想定とずれていても、DBはエラーを出さず、黙って何万行でも書き換えます。この記事では、危険なDB操作を実行前に確かめるやり方=dry runを、手軽な順に5段階で整理します。
dry run(ドライラン)とは
dry run とは、実際の変更を加えないまま、実行したら何が起きるかだけを確認する動作です。日本語では「空実行」「試験実行」とも呼ばれ、ファイル同期の rsync --dry-run のようにDB以外でも広く使われます。
DBで確かめたいのは、①範囲(何件が対象か)②内容(値がどう変わるか)③影響(時間とロック)の3つです。①②は簡単に確認できますが、③は方法を変えないと分かりません。ここを混同しないことが出発点になります。
方法① 同じ条件でSELECTしてから実行する
いちばん単純ですが、費用対効果は圧倒的です。破壊的なSQLの前に、まったく同じWHERE句でSELECTするだけです。
-- 1) 対象件数を数える
SELECT COUNT(*) FROM users WHERE last_login_at < '2025-01-01';
-- 2) 中身も数件見る(値が想定どおりか)
SELECT id, email, last_login_at FROM users
WHERE last_login_at < '2025-01-01' ORDER BY id LIMIT 20;
-- 3) 想定と一致したら、同じWHERE句のまま実行する
DELETE FROM users WHERE last_login_at < '2025-01-01';
コツはWHERE句を書き直さず、コピー&ペーストで完全一致させることです。「確認のときは AND deleted_at IS NULL が付いていたのに、本番では消えていた」——事故はこの1行の差から生まれます。
なお、MySQLクライアントには機械的な保険もあります。公式は --safe-updates(別名 --i-am-a-dummy)について「WHERE句でキーを使わない、またはLIMITを付けないUPDATE・DELETEはエラーになる」と説明しており、あわせてSELECTにも既定1,000行の制限が入ります。本番DBに入る接続では有効にしておく価値があります。
方法② トランザクションで実行してROLLBACKする
件数だけでなく実際に更新し、結果を見てから取り消す方法です。
BEGIN;
UPDATE orders SET status = 'canceled' WHERE created_at < '2025-04-01' AND status = 'pending';
SELECT ROW_COUNT(); -- 実際に何行変わったか
ROLLBACK; -- 変更を捨てる
ここに大きな落とし穴があります。この手法が確実に使えるのはDML(INSERT・UPDATE・DELETE)だけで、DDL(テーブル定義の変更)では製品によって前提が変わります。
| DML(UPDATE等) | DDL(ALTER TABLE等) | |
|---|---|---|
| MySQL | ROLLBACKで戻せる | 戻せない(暗黙のコミットが発生) |
| PostgreSQL | ROLLBACKで戻せる | 戻せる(一部例外あり) |
MySQL公式マニュアルは CREATE TABLE / ALTER TABLE / DROP TABLE などのDDLが暗黙のコミットを引き起こすと明記し、InnoDBの CREATE TABLE もユーザーのROLLBACKでは取り消せないと説明しています。つまりMySQLで BEGIN; ALTER TABLE ...; ROLLBACK; をやると、ALTERは戻らないうえその手前のINSERTまで確定します。
一方PostgreSQLはDDLもトランザクション内で扱えます。公式ドキュメントも「通常のCREATE INDEXはトランザクションブロック内で実行できるが、CREATE INDEX CONCURRENTLYはできない」と説明しています。
ROLLBACKしてもロックは掛かる
BEGINしている間、対象行やテーブルはロックされたまま。本番の巨大テーブルで開きっぱなしにすると他の処理が詰まり、dry runのつもりで障害を起こします。確認したら即ROLLBACK。
オートコミットに注意
クライアントによっては1文ごとに自動コミットされる設定になっている。BEGINしたつもりが効かず、そのまま本番反映される事故がある。実行前に設定を確認する。
方法③ フレームワークのdry run機能を使う
LaravelにはArtisanコマンドのオプションがあります。公式ドキュメントは「マイグレーションによって実行されるSQL文を、実際には実行せずに確認したい場合は --pretend を付ける」と説明しており、migrate と migrate:rollback の両方で使えます。
php artisan migrate --pretend # これから流れるSQLを表示するだけ
php artisan migrate:rollback --pretend # 戻すときのSQLも事前に見られる
分かるのは「どんなSQLが流れるか」だけです。本番のデータ量で何秒かかるか、途中で制約違反にならないかは分かりません。マイグレーション内でデータを読んで分岐する処理も、SQLを実行しない --pretend では正しく評価できないので、データ移行はマイグレーションに書かず別スクリプトに分けるのが安全です。詳しくはデータベースマイグレーションとは?本番反映で注意する理由で扱っています。
方法④ スキーマ変更ツールのdry run
大きなテーブルの ALTER TABLE は専用ツールを使うのが一般的で、これらは安全側の既定値を持っています。
pt-online-schema-change(Percona)
公式は --dry-run を「新しいテーブルを作成してALTERするが、トリガーの作成・行のコピー・元テーブルの入れ替えは行わない」と説明。ALTER文自体が通るかを検証できる。実行には --execute が必須。
gh-ost(GitHub)
公式は --execute について「このパラメータがなければ、マイグレーションはnoop(テーブル作成と妥当性のテストはするが、データには触れない)」と説明。既定が空実行という設計思想。
方法⑤ バックアップから戻した環境でリハーサルする
大量DELETE、列の型変更、巨大テーブルのALTERなど取り返しのつかない操作では①〜④では足りません。実行時間・ロックによる詰まり・戻し方が確認できないからです。
そこで本番のバックアップを検証環境にリストアし、同じ手順を通しで実行します。本番同等のデータ量で所要時間が測れ(「5分の想定が3時間」を事前に知れる)、戻す手順の練習にもなり、バックアップが本当に復元できるかの確認も同時にできます。件数の少ない検証環境で測った時間は当てになりません。
5つの方法の使い分け
| 方法 | 分かること | 分からないこと | 手間 |
|---|---|---|---|
| ① SELECTで確認 | 対象件数・対象データ | 更新後の姿、所要時間 | 最小 |
| ② BEGIN→ROLLBACK | 更新後の姿・変更行数 | MySQLのDDL、ロック影響 | 小 |
| ③ --pretend | 流れるSQLの中身 | 実データでの成否・時間 | 小 |
| ④ ツールの--dry-run | ALTERの妥当性 | 本番データでの所要時間 | 中 |
| ⑤ 復元してリハーサル | 時間・ロック・戻し方まで | (ほぼ実測できる) | 大 |
日常の小さな修正なら①、まとまった一括更新なら①+②、スキーマ変更なら③④、取り返しがつかない操作なら⑤まで——という段階分けが実務的です。
dry runの落とし穴(過信しない)
確認と実行の間にデータが変わる
SELECTした瞬間と実行の瞬間の間にアプリが行を追加すれば対象件数は変わる。動きのあるテーブルでは対象IDを先に固定してから更新する。
連鎖に気づけない
外部キーの ON DELETE CASCADE やトリガーがあると、1行のDELETEが別テーブルの数千行を消す。件数確認は「直接の対象」しか数えていない。
そして接続先の取り違え——検証のつもりが本番だった——は実際に起きます。実行直前にホスト名を目視してください。dry runは事故の確率を下げるだけで、バックアップの代わりにはなりません。
自作バッチにdry-runを付けるときの作法
データ補正スクリプトを自分で書くときは、既定をdry runにして --execute を明示したときだけ書き換える(gh-ostと同じ思想)と、うっかり実行を構造的に防げます。dry run時は件数だけでなく「先頭数件の変更前→変更後」まで出すこと。あわせて冪等性(2回流しても結果が変わらない)を意識しておくと、途中で失敗しても安全にやり直せます。
よくある質問
Q. dry runをすれば本番作業は安全ですか?
安全になりますが十分ではありません。分かるのは主に「対象と内容」で、ロック・実行時間・連鎖・環境差は別に確認が必要です。戻せるバックアップを取ってから実行する原則は変わりません。
Q. MySQLでALTER TABLEをROLLBACKできないのはなぜですか?
MySQLではDDLが暗黙のコミットを引き起こす仕様だからです。公式マニュアルは CREATE TABLE・ALTER TABLE・DROP TABLE などをその対象に挙げ、InnoDBのCREATE TABLEもユーザーのROLLBACKでは取り消せないと説明しています。MySQLでスキーマ変更を試すならコピーした検証用DBで行ってください。
Q. SELECTで確認したのに、実行したら件数が違いました。なぜ?
確認と実行の間に他の処理がデータを変えた可能性が高いです。NOW() など時刻を条件にしている場合も実行時刻の差でずれます。対象を固定したいときは、先に対象IDを作業テーブルへ退避し、そのIDに対してだけ更新するのが確実です。
Q. dry runとステージングでのテスト、どちらをやるべきですか?
役割が違うので置き換えにはなりません。ステージングは「手順とロジックが正しいか」を見るもので先に行い、本番でのdry runは「本番のいまのデータに対して、対象が想定どおりか」を見る最終確認です。
まとめ
DBのdry runは、実際には変更せず「実行したら何が起きるか」だけを先に確認するやり方です。①同じWHERE句でSELECT → ②BEGIN→ROLLBACK → ③--pretend → ④ツールの --dry-run → ⑤復元してリハーサルの順に、確実さと手間が増えます。操作の取り返しのつかなさに応じて、どこまでやるか決めてください。
覚えておきたいのは「MySQLのDDLはROLLBACKで戻せない」ことと、「dry runはバックアップの代わりにならない」ことの2つです。
参考リンク
- MySQL公式: Statements That Cause an Implicit Commit / mysql Client Options(--safe-updates)
- PostgreSQL公式: CREATE INDEX(トランザクションブロックとCONCURRENTLY)
- Laravel公式: Database: Migrations(--pretend)
- Percona公式: pt-online-schema-change(--dry-run / --execute)
- GitHub gh-ost: Command line flags(--execute)
- 関連記事: データベースマイグレーションとは?本番反映で注意する理由 / トランザクションとは?どんなときに必要? / ロールバックとは?切り戻しとの違い / RDBMSパフォーマンスチューニングの基礎
- 用語集: トランザクション / ロールバック / 冪等性 / ステージング