開発

Django migrateの実務|makemigrationsからの流れと本番適用・巻き戻し

Django migrateの実務|makemigrationsからの流れと本番適用・巻き戻し

Djangoのmigrateは、モデルを書き換えた結果をデータベースへ反映するコマンドです。開発中はmakemigrationsと2つ打てば済みますが、稼働中のサービスに当てる段になると話が変わります。どの順で流れるのか、途中で落ちたら何が残るのか、戻せる変更と戻せない変更をどこで見分けるのか。この記事では、2工程の分担、マイグレーションファイルの読み方、本番へ当てる段階分け、巻き戻しとsquash、失敗時の復旧までを実装の手順で整理しました。Django本体の位置づけとMVTはDjangoとは?Python製Webフレームワークの特徴・MVT・始め方を入門解説で扱います。

まとめ:django migrateの適用順序と巻き戻しの線引き

マイグレーションは、モデルの差分をファイルに落とす工程と、そのファイルをデータベースへ流す工程に分かれます。開発機では一体に見えますが、本番では時間も担当者も分かれるため、この2工程を別物として扱うところから始めます。

  • makemigrationsはモデルの差分をファイルへ書き、migrateはdjango_migrationsテーブルと突き合わせて未適用ぶんだけを流します。
  • 実行前の確認はsqlmigrateとmigrate --plan、状態の確認はshowmigrationsという2系統で足ります。
  • SQLiteとPostgreSQLでは全操作が既定で単一トランザクションに入りますが、MySQLとOracleではトランザクション無しで流れます。失敗したときの後始末の重さがここで分かれます。
  • 無停止で当てるなら、列の追加・両方書き・データ埋め・読み先の切り替え・旧列の削除を別のリリースへ割ります。
  • 逆方向はスキーマを戻すだけで、消えたデータは戻りません。本番では巻き戻しより前進で直すほうが安全な場面が多くあります。

採用の線引きは最終章で示します。先に言えば、データを伴わないスキーマだけの変更で、PostgreSQLで、まだ新コードが書き込みを始めていないなら、逆方向のmigrateで戻して構いません。

makemigrationsとmigrateが分担する2工程と生成物の置き場所

Djangoのマイグレーション系コマンドは4つです。makemigrationsがファイルを作り、migrateが当て、sqlmigrateが中身のSQLを見せ、showmigrationsが状態を並べます。

makemigrationsがモデル差分を読み取ってファイルへ書き出す範囲

makemigrationsは、INSTALLED_APPSに登録されたアプリのモデル定義と、既存のマイグレーションが表す状態とを比べ、差分をアプリ配下のmigrationsディレクトリへPythonファイルとして書き出します。番号は0001_initialから順に振られ、ファイル名の後半は変更内容から自動で決まります。読み手のために名前を付けたいときは--nameの指定が必要です。

python manage.py makemigrations myapp --name add_status_to_order

ここで生まれたファイルは、コードベースの一部としてコミットし配布する前提で設計されています。開発者ごとに別々のファイルが生まれると番号が衝突するため、モデルを触るブランチではマージ前にmakemigrationsを走らせ直す運用にしておくと事故が減ります。データだけを入れ替えたいときは--emptyで空のファイルを作り、自分で操作を書く方式です。

migrateがdjango_migrationsテーブルを見て未適用ぶんを流す

migrateは、接続先のデータベースにあるdjango_migrationsテーブルを読み、そこに記録が無いファイルだけを依存関係の順に実行します。引数を付けなければ全アプリ、アプリ名を付ければそのアプリ、さらに番号を付ければその地点までが対象です。番号が現在より前なら逆方向に動きます。

python manage.py migrate
python manage.py migrate myapp
python manage.py migrate myapp 0002

実行の前に何が起きるかを見るならmigrate --planです。公式の説明は「指定したmigrateコマンドで実行されるマイグレーション操作を示す」で、依存関係を解いた後の実行順が並びます。マイグレーションを持たないアプリのテーブルを作る--run-syncdbもありますが、公式自身が推奨しない旨を添えているため、既存プロジェクトの移行途中以外では使わない方針で構いません。

sqlmigrateとshowmigrationsで流す前の中身と状態を確かめる

sqlmigrateは、指定したマイグレーションが発行するSQLを標準出力に出します。本番へ当てる前に、テーブル書き換えを伴う文が含まれていないか、想定外のインデックス削除が混ざっていないかがここでの確認対象です。--backwardsを付けると、取り消す側のSQLも出力されます。戻せる変更かどうかを机上で確認する手段としてはこれが最短です。on_deleteだけを変えたときにDDLが出ない理由はDjango on_deleteの選び方:7種の挙動と6.1のDB_*系・削除設計で扱っています。

python manage.py sqlmigrate myapp 0002
python manage.py sqlmigrate myapp 0002 --backwards

showmigrationsは既定の--listでアプリごとの一覧を出し、適用済みには[X]が付きます。--planに切り替えると、Djangoがたどる適用の順序が並びます。環境ごとの適用地点を比べるなら前者、依存の解け方を疑うなら後者が確認対象です。

マイグレーションファイルの読み方:dependenciesとoperations

自動生成されたファイルを開くと、Migrationクラスにdependenciesとoperationsという2つのリストが並んでいます。前者が「いつ流れるか」を、後者が「何をするか」を決めます。この2つを読めば、そのファイルが本番で引き起こす内容はおおむね判断可能です。

dependenciesが決める適用順序と別アプリをまたぐときの書き方

dependenciesには、先に当たっていなければならないマイグレーションをアプリ名と番号の組で並べます。同じアプリ内では直前の番号が入るだけですが、外部キーで別アプリのモデルを参照すると、参照先アプリのその時点の最新マイグレーションが自動で足されます。ここを手で消せば、参照先のテーブルが無い状態で外部キーを張ろうとして失敗する結果です。

データ移行を自分で書くときも同じ考え方です。移行元と移行先が別アプリにまたがるなら、両方のアプリのマイグレーションを依存に並べます。initial = Trueが付いているファイルは初期マイグレーションとして特別に扱われ、後述の--fake-initialの対象になります。

RunPythonで履歴モデルを取り出す理由と現在のモデルを読まない理由

データを書き換えるマイグレーションはRunPythonで書きます。移行処理で使う一括書き込みメソッドの選び方は、Django ORMのクエリ設計にまとめています。ここで守る決まりが1つあります。関数の中で今のmodels.pyを読み込んではいけません。受け取ったappsからapps.get_model()で当時のモデルを取り出します。

def fill_status(apps, schema_editor):
    Order = apps.get_model("myapp", "Order")
    Order.objects.filter(status="").update(status="draft")

理由は単純で、現在のモデルを読むと、半年後にそのフィールドを消した時点で過去のマイグレーションが動かなくなるからです。新しい開発者が空のデータベースからmigrateを流したときに落ちる、という形で表面化します。apps.get_model()が返すのは、そのマイグレーション地点でのフィールド構成を持つモデルなので、後の変更に影響されません。

もう1つ、全件を1行ずつsave()で回すとクエリ本数が行数に比例します。数万行のテーブルでは、ここが移行時間の大半を占めます。update()やbulk_update()でまとめる、あるいは主キーの範囲で分割して流すのが実務の書き方です。クエリ本数が膨らむ構図そのものはN+1問題とは?仕組みと検出方法・ORM別の対策【実測クエリ数つき】で扱っています。

RunSQLのreverse_sqlの有無が巻き戻しの可否を決めている

生SQLを流すRunSQLには、順方向のsqlと逆方向のreverse_sqlを渡せます。後者を省くと、そのマイグレーションは戻せません。逆方向のmigrateを打った時点でIrreversibleErrorが出て止まります。RunPythonも同じで、第2引数に逆方向の関数を渡さなければ戻せません。

戻す必要が無いと分かっている操作でも、意図して戻せなくしたのか書き忘れたのかを後から区別できるよう、RunPython.noopやRunSQL.noopを明示的に置く書き方をおすすめします。

本番のデータベースへ無停止で当てるときの適用順序と変更分割の原則

ローリング更新では、旧コードと新コードが同時に動く時間帯が必ず生まれます。この前提を置くと、1回のリリースに入れてよい変更の範囲が自然に決まります。破壊的な変更を1本のマイグレーションに詰めないことが、無停止での最大の分かれ目です。

列の追加と参照の切り替えと削除を安全に別リリースへ割る進め方

列名を変えたい、型を変えたい、という要求は、そのまま当てると旧コードが落ちます。次の順で5回に割ると、どの段でも戻せる状態を保てます。

段 やること 戻し方
1 新しい列をNULL許容で追加 列を削除
2 新旧の両方へ書くコードを配る 前版へ戻す
3 既存行を新列へ埋める 再実行で足りる
4 読み先を新列へ切り替え 前版へ戻す
5 旧列を削除 戻せない

2段目から4段目はアプリのデプロイであってマイグレーションではありません。データベースの変更は1段目・3段目・5段目の3本に分かれ、それぞれが単独で適用できます。既定値を付けた列の追加は、データベースによっては既存行の書き換えを伴うため、行数が多いテーブルではNULL許容で足してから埋める設計です。無停止を壊す発生源の全体像はゼロダウンタイムデプロイとは?無停止を壊す発生源と設定順序・計測方法を実装者目線で解説で整理しています。

DDLトランザクションの可否がPostgreSQLとMySQLで分かれる点

公式ドキュメントは、DDLトランザクションに対応するデータベース(SQLiteとPostgreSQL)ではすべてのマイグレーション操作が既定で単一のトランザクション内で走り、対応しないデータベース(MySQL、Oracle)ではトランザクション無しで走る、と明記しています。この差は失敗時に効いてきます。

PostgreSQL側の接続設定と、開発用のSQLiteから移す際の手順はDjangoとPostgreSQLの接続設定にまとめています。

PostgreSQLなら、3つの操作を持つマイグレーションが2つ目で落ちたとき、1つ目もまとめて巻き戻ってファイルは未適用のまま残ります。MySQLでは1つ目が適用された状態でそこに残り、記録上は未適用のままです。次に同じファイルを流すと、すでにある列を作ろうとして再び落ちます。MySQL系を使う案件では、1マイグレーション1操作へ細かく割るだけで、この後始末がかなり軽くなります。

atomicを外す条件指定とロック時間を短く保つ操作の選び方

長いデータ移行を単一トランザクションに入れると、その間ずっとトランザクションが開いたままになります。Migrationクラスにatomic = Falseを置くとトランザクションから外れ、途中まで進んだ状態が残る代わりに、ロックの保持時間を短く保てます。

PostgreSQLでは、この指定が必須になる操作があります。django.contrib.postgres.operationsのAddIndexConcurrentlyとRemoveIndexConcurrentlyは、書き込みを止めずにインデックスを作成・削除しますが、公式はCONCURRENTLYオプションはトランザクション内で使えないと述べています。稼働中のテーブルへインデックスを足すなら、この操作を単独のマイグレーションに置き、atomic = Falseを添える指定が必要です。

チェック制約も同じ形です。AddConstraintNotValidはNOT VALID付きで制約を足して既存行の検証を省き、ValidateConstraintが後から検証します。公式は、この2つを別々のマイグレーションで実行すべきとしています。同じアトミックなマイグレーションに入れると通常の制約追加と同じ結果になり、非アトミックなマイグレーションに入れると検証に失敗したときデータベースが中途半端な状態になり得るためです。

巻き戻しとsquash:戻せる操作と戻せない操作を見分ける判断基準

逆方向のmigrateは、スキーマを前の形へ戻す機能であって、失われたデータを取り返す機能ではありません。ここを混同したまま本番で打つと、傷を広げます。

番号指定とzeroで戻したときにデータベースで実際に起きること

アプリ名に続けて0002のように番号を渡すと、0003以降が逆順に取り消されます。zeroを指定すると、そのアプリのマイグレーションがすべて取り消され、テーブルごと消えます。開発機のやり直しには使えますが、本番で打つ場面はまずありません。

注意すべきは、列を削除したRemoveFieldを戻したときです。列は復活しますが、中身は空です。つまり逆方向で戻しても、削除の瞬間に消えたデータは戻りません。データを伴う変更を戻す必要があるなら、逆方向のmigrateではなくバックアップからの復元を検討する場面だ、と切り替えて考えます。

squashmigrationsがRunPythonを畳む条件とelidableの指定

年単位で運用したアプリではマイグレーションが数百本になり、テスト時のmigrateだけで時間を取られます。squashmigrationsは、指定した範囲を1本のファイルへまとめ直すコマンドです。

python manage.py squashmigrations myapp 0004 --squashed-name initial_squashed

まとめる過程で、Djangoは操作の並びを縮めます。モデルを作ってから消す組み合わせは相殺され、フィールドの追加と変更はまとめられます。ただしRunSQLとRunPythonは、elidableとしてマークされない限り通り抜けできません。過去のデータ移行がそのまま残るため、期待したほど本数が減らない原因はたいていここです。すでに全環境で当たり終えたデータ移行にはelidable=Trueを付けておくと、次回のsquashで畳めます。縮める処理そのものを止めたいときは--no-optimizeを使います。

生成されたファイルにはreplaces属性が入り、旧ファイルと共存します。全環境が新しい1本を通過したら旧ファイルを消せます。Django 6.0のリリースノートには、squash済みのマイグレーションを通常のマイグレーションへ移行する前にさらにsquashできるようになった、という項目が入りました。旧いsquashをまだ通していない環境が残っている場合の逃げ道になります。ファイルを消して名前を再利用する可能性があるなら、migrate --pruneでマイグレーションテーブルから参照を落としておきます。

巻き戻しより前進で直すほうが安全になる本番運用場面の判断基準

逆方向のmigrateを選んでよいのは、次の3つがすべて成り立つときだけだと考えています。第一に、対象がスキーマだけでデータの削除を伴わないこと。第二に、DDLトランザクションに対応したデータベースであること。第三に、新しいコードがまだその列へ書き込みを始めていないこと。

逆に、MySQL系である、すでに新列へ書き込みが走っている、データ移行が途中まで進んでいる、のいずれかに当たるなら、戻さずに前へ進めます。つまり、現状を打ち消す新しいマイグレーションを書いて当てます。履歴は増えますが、記録と実体のずれが生まれない点で、本番では扱いやすい選択です。

失敗したマイグレーションの復旧手順とfake指定を使う判断条件

途中で落ちたとき、最初にやることは修正ではなく現状の把握です。ファイルの内容、Djangoの記録、実際のスキーマという3つがどこでずれたのかを特定してから手を入れます。

途中で落ちたときに突き合わせる3つの情報と実際の詳細確認順序

まずshowmigrations --listで、どこまで[X]が付いているかを見ます。次にsqlmigrateで、落ちたファイルが流そうとしたSQLを取り出します。最後に、データベース側で実際のテーブル定義を確認する順序です。PostgreSQLならpsqlのテーブル記述コマンド、MySQLならSHOW CREATE TABLEです。

この3つを並べると、状況は2つのどちらかに落ちます。SQLが1つも通っていないなら、原因を直してもう一度migrateを打てば完了です。一部だけ通っているなら、通ったぶんを手で戻すか、通っていない残りだけを実行する新しいファイルを書きます。トランザクション無しで走るデータベースでは後者になりやすい、という前章の話がここへつながります。

fakeとfake-initialを使ってよい場面と壊してしまう場面

migrate --fakeは、対象までのマイグレーションを適用済みとして記録するだけで、スキーマを変えるSQLを実行しません。使ってよいのは、実際のスキーマがそのファイルの適用後と一致していると自分の目で確認できたときだけです。手作業でテーブルを直した後に記録だけ合わせる、という場面が典型です。

逆に、確認せずに--fakeで先へ進めると、その後のすべてのマイグレーションが存在しない列を前提に動き、次のリリースでまとめて落ちます。復旧作業の最中に時間を惜しんでここを飛ばすと、傷が数倍になります。

--fake-initialは用途が別です。すでにテーブルがあるデータベースへ初期マイグレーションを乗せるとき、対象のテーブルがすでに存在する場合にモデル作成の適用を飛ばします。Djangoの管理下に無かった既存データベースを、マイグレーションの仕組みへ載せ替える初回にだけ使うオプションだと考えて構いません。

CIでmakemigrationsのcheck指定を回して取り残しを防ぐ

本番で起きる事故のうち、かなりの割合が「モデルを変えたのにファイルを作り忘れた」に由来します。これは機械で止められます。makemigrations --checkは、マイグレーションの無いモデル変更を検出したとき非ゼロで終了し、--dry-runを含むためファイルを書きません。プルリクエストのCIに1行入れておくだけで、取り残しがマージされなくなります。

python manage.py makemigrations --check --dry-run

デプロイ手順の側にはmigrate --planを挟みます。当てる前に何が流れるかが出力に残るので、想定外のファイルが混ざっていれば停止する判断が可能です。この2つと、前章の段階分けを手順書に落としておくと、担当者が替わっても同じ判断ができます。マイグレーションを含めたリリース全体の並べ方はgunicorn+nginx構成と本番設定から見たDjangoのデプロイ手順で、collectstaticやプロセス入れ替えとの前後関係まで整理しています。稼働中のシステムでこうした運用を組み立てる場面や、社内チームへ引き継ぐ体制づくりは保守運用・内製化支援でご相談を承っています。

よくある質問

makemigrationsで「変更なし」と出るのはなぜですか?

アプリがINSTALLED_APPSに入っていない、migrationsディレクトリに初期化ファイルが無い、モデルをmodels.py以外の場所に書いて読み込まれていない、のいずれかであることが大半です。アプリ名を明示してmakemigrations myappと打つと、対象が見つからない場合にその旨が出るため切り分けが早くなります。

マイグレーションファイルはGitに入れるべきですか?

Gitへ入れるのが原則です。公式ドキュメントも、マイグレーションはコードベースの一部としてコミットし配布されるよう設計されていると述べています。除外すると、環境ごとに別々のファイルが生成され、同じ番号で中身が違うという状態になります。

本番でmigrateを流すのはデプロイの前と後のどちらですか?

旧コードでも動く前方互換の変更(NULL許容の列追加など)ならデプロイ前に流します。旧コードが壊れる変更なら、そもそも1回で当てず本文の5段階に割ります。判断の基準は前と後ではなく、旧コードが当たった後のスキーマで動き続けるかどうかです。

開発中にマイグレーションファイルを消して作り直してよいですか?

未適用で、まだ他の開発者へ配っていないなら消して構いません。共有済みのファイルを消すと、すでに当てた環境で記録だけが残ります。この場合はmigrate --pruneでマイグレーションテーブルの参照を落としてから作り直します。

Djangoのバージョンで手順は変わりますか?

4つのコマンドとdependencies・operationsという骨格は長く変わっていません。差分としては、Django 6.0でsquash済みのマイグレーションをさらにsquashできるようになりました。2026年8月時点で最新は6.1系(2026年8月5日リリース・対応Pythonは3.12から3.14)、長期サポートは5.2系で拡張サポートが2028年4月までとなっています。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

ほか 1 件の記事からもリンクされています。

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.09 テックブログ IDCFクラウド(IDCフロンティア)不正アクセス・ランサムウェア:影響先・復旧・データは戻るか
  2. 2025.11.04 テックブログ Runpodとは?料金・使い方・支払い方法とGPUクラウドの選び方【2026年10月】
  3. 2026.01.09 コラム 違背実験とは?読み方・意味と具体例、ガーフィンケル原典の数値とエスノメソドロジー
  4. 2026.10.09 テックブログ ニッスイのサイバー攻撃で日水物流の入出荷停止|委託先クラウド障害に荷主が備える手順

RELATED POSTS 関連記事

目次