iimon TECH BLOG

iimonエンジニアが得られた経験や知識を共有して世の中をイイモンにしていくためのブログです

Djangoのマイグレーションを初心者なりに理解する

こんにちは、木村です!最近Djangoに触れる機会がちょくちょくあるんですが、DB操作でいつもおっかなびっくりやっています。対応する時にそれなりに調べてはいるんですが、毎度微妙に自信が持てずにいます。

せっかく触る機会をもらったのだから初心者なりにDjangoでのマイグレーション操作について調べたことをまとめました。よろしくお願いします!

Django

そもそもDjangoとは何か。Pythonで実装されたWEBアプリケーションフレームワークです。認証や管理画面、DB操作などのWeb開発で必要なものを揃えています。

特徴として挙げられるのは主に次の二つです。

MTV(Model/Template/View)

Djangoで採用されているアーキテクチャです。よくセットでMVCというのが紹介されますが、ほぼ同じようなものと捉えて問題なさそうです。MVCとの対応については公式のFAQでも説明されています。

https://docs.djangoproject.com/ja/6.0/faq/general/

Model : データ構造の定義とDBとのやり取りを行います

Template : ユーザーに見せる見た目を構成するコンポーネント。HTMLなどの表示機能を担う

View : リクエストを受け取り、Modelからデータを取得し、Templateに渡すコンポーネント

ORM(Object-Relational Mapping)

DjangoはORMを採用しています。

オブジェクト指向プログラミング言語で記述されたアプリケーションを、基盤となるDBに接続して操作する仕組みです。SQLによる直接制御を行わずにDBを操作するのでSELECT文などを書きません。

DjangoではORM操作をModelクラスを介して行います。クラスに用意されたメソッドを使用してDB構造を定義し、操作を行います。具体的には、1つのModelクラスが1つのテーブルに対応してクラスの属性がそのままテーブルの列になります。つまりテーブルのスキーマがPythonのModelクラスとして定義されています。

Userテーブル

id name age registration
xxx TanakaIchiro 21 1月1日
xxx YamadaHanako 30 6月6日
from django.db import models

class User(models.Model):
    name = models.CharField(max_length=100)
    age = models.IntegerField()
    registration = models.DateField()

※Djangoでは自動でIDが付けられます。後述。

(余談1)Modelクラス

ちなみにModelクラスというものが出てきましたが、これがどこで定義されているのか気になったので、Djangoのソースコードを実際に追ってみました。

https://github.com/django/django/blob/6.0/django/db/models/base.py

このコードの models.Model は、こちらで定義されているクラスで、from django.db import models で読み込んでいます。

https://github.com/django/django/blob/6.0/django/db/models/base.py#L481

User(models.Model) のように継承することで、save() などのDB操作メソッドや、後述するマイグレーションの仕組みが使えるようになっています。

フィールドの型を指定するmodels.CharField などのフィールドも同じく、django/db/models/fields/__init__.py で定義されていることが確認できます。

# django/db/models/fields/__init__.py
class CharField(Field):      # 1204行目
class DateField(...):        # 1422行目
class IntegerField(Field):   # 2062行目

https://github.com/django/django/blob/6.0/django/db/models/fields/init.py#L1204

マイグレーションとは

Migration、直訳すると移住・移転・移動ですが、一般的にIT分野では既存システムやソフトウェア、データなどを別の環境に移転したり、新しい環境に移行することを指しているようです。

Djangoでのマイグレーションは

マイグレーション (Migrations) は、Django でモデルに対して行った変更 (フィールドの追加やモデルの削除など) をデータベーススキーマに反映させる方法ですhttps://docs.djangoproject.com/ja/6.0/topics/migrations/

とのこと。

また具体的には

マイグレーションというのは、データベーススキーマに対するバージョン管理システムのようなものです。makemigrations はモデルの変更点を1つのマイグレーションファイルにパッケージングし(コミットのようなものです)、migrate はその変更点をデータベースに適用する、というわけです。

各アプリのマイグレーションファイルはそのアプリの "migrations" ディレクトリの中に保管され、コードベースの一部としてコミットされ、配布されるようにデザインされています。いったん開発用マシンでマイグレーションファイルが作成されれば、その後、チームメンバーのマシンやステージング環境のマシン上で同一のマイグレーションが行われ、最終的にプロダクション環境でも同じマイグレーションが行われます。 https://docs.djangoproject.com/ja/6.0/topics/migrations/

というように説明されていました。

DjangoでModelを作成してマイグレーションのコマンドを打つと、このような感じでファイル(0001_initial.py)が作成されます。

マイグレーションって実際何やってるの

例えば、Userモデルに「出身地(birthplace)」を追加するとします。そうすると、DBにカラムを追加する作業が必要になります。直接DBをSQLで操作するなら

ALTER TABLE users_user ADD COLUMN birthplace varchar(100);

といった感じですが、先述の通りDjangoではORMなのでコード中に直接クエリを記述しません。コードで記述したスキーマをDBに実際に反映させる必要があります。その反映操作がマイグレーションです。

  1. マイグレーション(ファイル)を作成する

    python manage.py makemigrations

    前回マイグレーションした時からの差分を自動で読み取って、DBを更新するための情報を記述したファイルを作成してくれます。

  2. マイグレーションを適用する

    python manage.py migrate

    この操作で初めてDBの構造を変更しています。

差分はどのように取られるか

なんとなく現在のDBの状態と比較するか、一番新しいマイグレーションファイルの状態から読み取って比較しているのだと思っていたのですが、実際はDBの実態を見るのではなく、これまでのマイグレーション履歴から再現したモデルの状態と、現在の models.py のモデルを比較して差分を取っているようでした。

この時に作られるメモリ上のデータ構造は、新しいモデルと現在のマイグレーションの状態の差分を計算するのにも使われます。Django は、メモリ上のモデルの集まりのすべての変更点を順番にたどって行き、最後に makemigrations した時のモデルの状態を理解します。そして、そのモデルと models.py ファイルにあるモデルとを比較し、行った変更に対して作業を行うのです。 https://docs.djangoproject.com/ja/6.0/topics/migrations/

つまり、これまでのマイグレーションファイル(0001, 0002, …)を頭から順番に適用していくことで、「前回までのモデルの状態」をメモリ上に組み立て直します。その組み立てた状態と、現在の models.py のモデルの状態を比較して、差分を見つけているようです。

(余談2)IDはどこで自動的に付けられるのか

先ほどの User モデルでは id を書いていませんが、テーブルには id カラムが作られていました。これはDjangoが自動で付けてくれているものです。これも仕組みが気になったので、Modelクラスと同じようにソースコードを追ってみました。

具体的には、class User(models.Model): というモデルクラスがPythonに読み込まれた時点で行われているようです。Modelクラスは ModelBase を継承していて、モデルクラスが定義されるとこの ModelBase.__new__ が呼ばれます。

# django/db/models/base.py
class ModelBase(type):
    """Metaclass for all models."""

    def __new__(cls, name, bases, attrs, **kwargs):
        ...
        new_class._prepare()   # ← ここで _prepare() を呼んでいる
        new_class._meta.apps.register_model(...)
        return new_class

https://github.com/django/django/blob/6.0/django/db/models/base.py#L97

この中で呼ばれている _prepare() の中身が、django/db/models/options.py にあります。

# django/db/models/options.py
def _prepare(self, model):
    ...
    if self.pk is None:  # 主キーがないときに
        ...
        # デフォルトの主キークラスを取得して
        pk_class = self._get_default_pk_class()
        # 「ID」カラムを主キーとするクラスを作成して
        auto = pk_class(verbose_name="ID", primary_key=True, auto_created=True)
        # 自動的にモデルに設定する
        model.add_to_class("id", auto)

(コメントアウトは私が追加したものです)

https://github.com/django/django/blob/6.0/django/db/models/options.py#L285

ここで「主キーが無ければ id を追加する」という処理が行われています。

※このとき追加される id の型は _get_default_pk_class() で決まり、その元になるのが DEFAULT_AUTO_FIELD という設定です。明示的に設定しなければ、Djangoのデフォルト値が使われます。

# django/conf/global_settings.py
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

https://github.com/django/django/blob/6.0/django/conf/global_settings.py#L440

そのため、今回のマイグレーションファイルでも idBigAutoField(大きな整数の自動採番)として作られています。

変更の反映はどのように行っているのか

migrate でDBが変更されるということでしたが、マイグレーションファイルとDBの間で実際に何が起きているのかを見ていきます。

マイグレーションファイルの中身

先ほどの例のように、User モデルに出身地(birthplace)フィールドを追加して makemigrations を実行すると、0002_user_birthplace.py というファイルが作られます。中身はこんな感じです。

from django.db import migrations, models

class Migration(migrations.Migration):

    dependencies = [
        ('users', '0001_initial'),
    ]

    operations = [
        migrations.AddField(
            model_name='user',
            name='birthplace',
            field=models.CharField(blank=True, max_length=100, null=True),
        ),
    ]
  • operations は、このマイグレーションで行う操作です。
    • user モデルに birthplace というフィールドを追加する(→AddField
  • dependencies は、このマイグレーションより前に適用されている必要があるマイグレーションです。
    • (0001_initial の後に実行

Django が (Python モジュールとして) マイグレーションファイルを読み込んだ時に最初に探すのは、Migration という名前の django.db.migrations.Migration のサブクラスです。そして、このサブクラスの4つの属性を調べますが、ほとんど場合に使われるのは、次の2つの属性です。

  • dependencies は、このマイグレーションが依存する他のマイグレーションのリストです。
  • operations は、このマイグレーションが行う操作を定義している Operation クラスのリストです。

operations がポイントです。これは、宣言的な命令の集まりで、Django にどんなスキーマの変更が必要かを教えます。Django はそれらをスキャンして、全アプリへのスキーマの変更を完全に表現するデータ構造をメモリ上に作り上げ、これを利用して、Django スキーマを実際に変化させる SQL 文を生成します。 https://docs.djangoproject.com/ja/6.0/topics/migrations/

SQLはいつ作られるのか

結論、migrate コマンドが実行されたタイミングで作成・実行されています。

公式ドキュメントによると、Djangoのマイグレーションの仕組みは2つの部分に分かれているそうです。

Django のマイグレーションシステムは2つの部分に分かれています。データベースに対してどのような操作を実行するべきかを計算してその結果を保管するロジックの部分 (django.db.migrations)と、「モデルを作成する」や「フィールドを削除する」といった操作を SQL に変換する、データベースの抽象レイヤーの部分です。後者の仕事を担当するのが、 SchemaEditor です。 https://docs.djangoproject.com/ja/6.0/ref/schema-editor/

つまり、マイグレーションファイルに書かれた AddField (カラムの追加)のような操作は、SchemaEditor というものがSQLに変換していて、この SchemaEditor はデータベースごとに用意されています。

Django のデータベースのバックエンドは、それぞれ対応するバージョンの SchemaEditor を提供しており、好きなときに connection.schema_editor() コンテキストマネージャを使ってアクセスできます。 https://docs.djangoproject.com/ja/6.0/ref/schema-editor/

MySQLにはMySQL用、PostgreSQLにはPostgreSQL用、というように、使用しているDBによって、対応した操作を自動で変換してくれるようです。(今回はMySQLを使っています)

実際に組み立てられるSQLは、sqlmigrate コマンドで確認できます。これはマイグレーションを実行せずに、その時点で実行される予定のSQLを表示してくれます。

python manage.py sqlmigrate users 0002
# → ALTER TABLE `users_user` ADD COLUMN `birthplace` varchar(100) NULL;

どのマイグレーションを適用したかはDBに記録されている

migrate を実行すると、上のSQLがDBに対して実行され、カラムが追加されます。

データベースの状態を、現在のモデルとマイグレーションのセットを基づいて同期します。 https://docs.djangoproject.com/ja/6.0/ref/django-admin/#django-admin-migrate

このとき、Djangoは django_migrations というテーブルに、適用したマイグレーションを記録しています。

id app name applied
19 users 0001_initial 2026-06-13 10:59:47
20 users 0002_user_birthplace 2026-06-14 11:52:09

0001_initial の後に 0002_user_birthplace が記録されています。

マイグレーションを行ったかどうかについてもdjango_migrations に記録されているので、すでに適用しているマイグレーションは実行されません。

Djangoは初期マイグレーションがあり、かつ作成しようとするテーブルが既に存在することを検出し、マイグレーションを既に適用済みとしてマークします。 https://docs.djangoproject.com/ja/6.0/topics/migrations/

最後に

なんとなくふんわりしたイメージで行っていたマイグレーションについて、少しわかった気がします。少なくとも「おっかなびっくりとりあえずマイグレーション」はこれからやらなくて良さそうです。その場その場で調べながらやっていたのでなかなかちゃんとした知識が定着していなかったなと反省しました。AIの台頭でインプットの時間が多くなっていますが、自分の言葉でアウトプットする時間を設けることで定着させていきたいです。

現在弊社ではエンジニアを募集しています! この記事を読んで少しでも興味を持ってくださった方は、ぜひカジュアル面談でお話ししましょう!

iimon採用サイト / Wantedly / Green

参考・引用

https://docs.djangoproject.com/ja/6.0/topics/migrations/

https://gw-python.com/archives/149

https://qiita.com/kotayanagi/items/01e9a617571e2b9526bc

https://aws.amazon.com/jp/what-is/object-relational-mapping/

https://www.itmanage.co.jp/column/about-migration/