6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【初心者】一番丁寧にデータベースのマイグレーションとORMapperを理解したい(マイグレーション編)

Posted at

はじめに

学生時代、研究などでデータベース(DB)を使う際は生の SQL を用いていました。DBeaver や pgAdmin のようなツールを使い、その CREATESELECT のような構文を用いていたのです。

しかしながら、社会人になり業務としてソフトウェアの作成に携わると、そのような DB の操作をすることはあまりないという現実に直面しました。
つまり、サービスとして高品質なものを作成する必要がある場合、マイグレーションツールや ORMapper といったツールも用いることが多い、という事実にそのときはじめて直面したのです。
そもそも、マイグレーションや ORMapper という言葉を聞いたことがない。
一見したところ、SQL を書くより複雑で難しそうに見える...理解できるのか不安...
そうした経験を踏まえて、マイグレーションと ORMapper についてできるだけわかりやすくどういうものであるのかを自分への備忘も兼ねて残したいと思います。

また、記事が長くなってしまったので、「マイグレーション」にのみ注力し、ORMapper については別記事にします。

マイグレーションや ORMapper という概念に慣れている方にとっては冗長なものと思われますが、ご容赦くださるとありがたいです🙇

環境はこのようなものです。

  • OS:WSL2(Ubuntu 24.04.3 LTS)
  • 開発環境の管理:mise
  • 言語:Python
  • パッケージ管理:uv
  • データベース:PostgreSQL

mise は以下から入手できます。

マイグレーション(ツール)とは

データベースという文脈におけるマイグレーションとは「データベース構造のバージョン管理」というような意味を持ちます。
より細かく述べるならば「時間軸上での状態変化を前提に、論理的一貫性・順序性・再現性を保証しながら構造(スキーマ)の遷移を管理すること」というような意味といえます。
「マイグレーション(migration)」という英単語の一般的な訳である「移行」と比べるとずいぶんと異なる概念に思え、これもまた理解を難しくする一因かと思います(私にとってはそうでした)。

つまりマイグレーションツールとは、先に述べたような概念を実現するツールということになります。マイグレーションツールを使うことで、単に SQL 文を実行してデータベースやテーブルを作成することに加えてより良いメリットが得られることになるはずです。そのメリットは何でしょうか。

  • スキーマ構造の変更履歴が残り、「いつ・なぜ・何を変えたか」を追跡できる
  • 構造変更を前提とした設計になり、一貫性があるためロールバックや安全な変更が可能になる
  • 構造をコードとして管理でき、環境差分を再現可能にできる

といった点があげられると考えられます。

Python でマイグレーションツールを使ってみる

Python でマイグレーションを行うツールとして、「Alembic」というツールがあります。
このツールを用いて、マイグレーションを体験してみます。

Alembic のインストール

uv のコマンドで Alembic をインストールします。以下のコマンドを実行します。

uv add alembic

Alembic のセットアップ

alembic init <環境名> コマンドを実行して、セットアップを行います。
例として、alembic init migrations というコマンドで実行します。するとプロジェクトに次のような構成が作成されてセットアップが行われます。

project_root/
├── alembic.ini          # Alembic設定ファイル
└── migrations/          # マイグレーション管理ディレクトリ
    ├── env.py           # マイグレーション実行環境の設定
    ├── script.py.mako   # マイグレーションファイルのテンプレート
    ├── README           # Alembic使用方法の説明
    └── versions/        # マイグレーションスクリプト格納ディレクトリ

alembic.ini を編集してデータベースに接続できるようにする

セットアップしたことで作成された alembic.ini を開くと次のように書かれた行があります。

sqlalchemy.url = driver://user:pass@localhost/dbname

これは、Alembic がデータベースの構造を変更するために実際にデータベースへアクセスするために必要な「接続文字列」になります。
今回 PostgreSQL を用いるため、Python で PostgreSQL に接続するためのプラグインツール「psycopg」を利用します。そのために、以下のコマンドを実行してインストールします

uv add psycopg[binary]

インストールに成功したら、alembic.ini を編集します。以下のような形式で記述します。

sqlalchemy.url = postgresql+psycopg://<ユーザー名>:<パスワード>@<ホスト>:<ポート>/<データベース名>
  • <ユーザー名>: PostgreSQL のユーザー名(例: postgres)
  • <パスワード>: PostgreSQL のパスワード(例: postgres)
  • <ホスト>: データベースサーバーのホスト名(例: localhost)
  • <ポート>: PostgreSQL のポート番号(例: 5432)
  • <データベース名>: 接続先のデータベース名(例: myapp_db)

マイグレーションファイルの作成

以下のコマンドでマイグレーションファイルを作成します。

alembic revision -m <作成したいマイグレーションファイルの名前>

コマンドを実行すると project_root/migrations/versions の下にマイグレーション用のファイルが作成されます。
ファイル名のフォーマットについては、alembic.ini の「file_template」で編集できます。
デフォルトでは<リビジョンID>_<作成したいマイグレーションファイルの名前>.pyになります。

project_root/
├── alembic.ini
└── migrations/
    ├── env.py
    ├── script.py.mako
    ├── README
    └── versions/
        ├── <リビジョンID>_<作成したいマイグレーションファイルの名前>.py
        └── ・・・・

実際に作成されたファイルの中身を見てみると、upgrade()downgrade() という関数が用意されていることがわかります。
この中に更新したいデータベースの構造についての定義を記述していきます。

"""create_master_schema

Revision ID: 4fa7b3c6326f
Revises:
Create Date: 2026-01-21 20:31:23.333276

"""

from collections.abc import Sequence

from alembic import op

# revision identifiers, used by Alembic.
revision: str = "4fa7b3c6326f"
down_revision: str | Sequence[str] | None = None
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None


def upgrade() -> None:
    """Upgrade schema."""
    pass

def downgrade() -> None:
    """Downgrade schema."""
    pass

マイグレーションファイルの中身を記述する

先の項で作成されたマイグレーションファイルの中身を記載しました。ここでは実際にコードを修正してみます。
upgrade()downgrade() を更新してみました。内容としては upgrade() で「master」というスキーマを作成するもので、downgrade() で master スキーマを削除するというものです。

"""create_master_schema

Revision ID: 4fa7b3c6326f
Revises:
Create Date: 2026-01-21 20:31:23.333276

"""

from collections.abc import Sequence

from alembic import op

# revision identifiers, used by Alembic.
revision: str = "4fa7b3c6326f"
down_revision: str | Sequence[str] | None = None
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None


def upgrade() -> None:
    """Upgrade schema."""
    op.execute("CREATE SCHEMA master;")


def downgrade() -> None:
    """Downgrade schema."""
    op.execute("DROP SCHEMA master CASCADE;")

マイグレーションを実行する

次のコマンドでマイグレーションを実行します。このコマンドは、現在の DB の状態から出発して、マイグレーションファイルの最新のバージョン(head)まで、未適用のマイグレーションを順番に適用します。

alembic upgrade head

これまでの例を参考にすると、このコマンドを実行することで、先程作成した master スキーマを作成するマイグレーションファイルが適用され、データベースに master スキーマが作成されます。

マイグレーションの状態を戻す

スキーマやテーブルを作成した後、間違った...戻したい...となった場合には次のコマンドを実行します。
-1 は何個前の状態に戻すかの指定です。-1 の場合は 1 つ前の状態に戻すという意味です。

alembic downgrade -1

例えば、続けてこのコマンドを実行すると、Alembic が参照している head の位置にあるマイグレーションファイルの downgrade() が実行されて master スキーマが削除されます。

図で理解する upgrade / downgrade

upgrade / downgrade を理解するポイントは 次の 2点 と考えています。

  1. リビジョンは一直線に並んでいる(古い → 新しい)
  2. コマンドで「いまの位置」が移動する

リビジョンの並び

マイグレーションファイルを作成するたびに、リビジョン(版)が増えていきます。
これらは時系列で一直線に並んでおり、一番新しいリビジョンを head と呼びます。


upgrade = 右に進む(新しい方向へ)

alembic upgrade head は、いまの位置から head まで一気に進むコマンドです。

実行前: まだ何も適用していない状態

グレー = まだ適用されていない

alembic upgrade head 実行後: 全て適用された状態

緑 = 適用済み、太枠 = 現在位置


downgrade = 左に戻る(古い方向へ)

alembic downgrade -1 は、1 つ前のリビジョンに戻る コマンドです。

実行前: ③まで適用済み

alembic downgrade -1 実行後: ③が取り消され、②の状態に戻る

点線グレー = 未適用(downgrade で戻した)


まとめ

コマンド 動き 実行される関数
alembic upgrade head ➡️ 最新まで進む upgrade()
alembic upgrade +1 ➡️ 1 つ進む upgrade()
alembic downgrade -1 ⬅️ 1 つ戻る downgrade()
alembic downgrade base ⬅️ 初期状態まで戻る downgrade()

補足:現在の状態を確認するコマンド

ここまで図で「📍 いまここ」と表現してきた現在位置は、実際のコマンドで確認できます。

alembic current - いまどこにいる?

alembic current

このコマンドを実行すると、現在データベースに適用されているリビジョン(= 図の「📍 いまここ」)が表示されます。

出力例:

a1b2c3d4e5f6 (head)  ← 最新まで適用済みの場合
7f8e9d0c1b2a         ← 途中のリビジョンにいる場合

💡 つまり 「いまここ」= alembic current の結果 です


alembic history - リビジョン一覧を見る

alembic history

このコマンドを実行すると、全リビジョンの一覧が表示されます。図でいう「①→②→③」の並びを確認できます。

出力例:

a1b2c3d4e5f6 -> 7f8e9d0c1b2a (head), add_column
7f8e9d0c1b2a -> 4fa7b3c6326f, add_table
4fa7b3c6326f -> None, create_schema       ← 最初のリビジョン

💡 --verbose オプションをつけると、作成日時などの詳細も表示されます

マイグレーションファイルを自動生成する

これまでの例では、op.execute("CREATE SCHEMA master;") のように SQL を直接書きました。
しかし、テーブルが増えてカラムが何十個もある場合、すべて手書きでコードを書くことは現実的でありません。
Alembic にはモデル定義から自動的にマイグレーションファイルを生成する機能があります。
これを使うには、まず SQLAlchemy というライブラリでモデル(テーブル構造)を定義する必要があります。
この SQLAlchemy こそが、タイトルにもある ORMapper です。
ただ、ここでは ORMapper については詳しく述べず、このライブラリを使う、ということにとどめます。

まず、SQLAlchemy をインストールします(Alembic と一緒に入っている場合もあります)。

uv add sqlalchemy

「モデル」を定義するファイルを作成する

ここでの「モデル」とはデータベースのテーブルなどの構造をコードで表現したもの、を表しています。
このコード例だと class User(Base) で定義される「ユーザー」テーブルの構成が「モデル」として表現されています。

# models.py
from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy.orm import declarative_base
from datetime import datetime

Base = declarative_base()

class User(Base):
    __tablename__ = "users"
    __table_args__ = {"schema": "master"}  # masterスキーマに作成

    id = Column(Integer, primary_key=True)
    name = Column(String(100), nullable=False)
    email = Column(String(255), nullable=False, unique=True)
    created_at = Column(DateTime, default=datetime.now)

コードについての説明を表にすると、重要なものは以下のようになるかと考えられます。
このように、SQLAlchemy を用いることで、テーブル構造を「モデル」としてコード化ができます。

コード 意味
Base = declarative_base() SQLAlchemy のモデルの「土台」を作成
class User(Base) Base を継承し「これは DB テーブルの定義である」という Class を宣言
tablename = "users" 実際のテーブル名を指定
Column(Integer, primary_key=True) 整数型の主キーカラム
Column(String(100), nullable=False) 100 文字までの文字列、NULL 不可

env.py を編集してモデルを認識させる

Alembic でマイグレーションファイルの自動生成を行うには、先ほど定義したモデルを認識させる必要があります。
migrations/env.py を開き、以下のように編集します。
このとき、import のパスはご自身の環境に合わせて指定する必要があります。

# env.py の上部に追加
from models import Base  # 作成したモデルをインポート

# target_metadata を設定(もともと None になっている部分を変更)
target_metadata = Base.metadata

これで、Alembic は Base に紐づくすべてのモデル(テーブル定義)を認識できるようになります。

autogenerate でマイグレーションファイルを自動作成

以下のコマンドを実行してマイグレーションを作成します。

alembic revision --autogenerate -m "create_users_table"

--autogenerate オプションがポイントです。このコマンドを実行すると、Alembic が現在の DB の状態とモデル定義を比較し、差分を検出してマイグレーションファイルを自動生成します。

生成されたファイルの例として、以下のように出力されます。手書きすることなく、モデル定義から自動的にマイグレーションが作成されました!

"""create_users_table

Revision ID: cc1182b38ab6
Revises: 690408bec79e
Create Date: 2026-01-28 10:42:21.152731

"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa
import sqlmodel.sql.sqltypes


# revision identifiers, used by Alembic.
revision: str = 'cc1182b38ab6'
down_revision: Union[str, Sequence[str], None] = '690408bec79e'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
    """Upgrade schema."""
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table(
        'users',
        sa.Column('id', sa.Integer(), nullable=False),
        sa.Column('name', sa.String(length=100), nullable=False),
        sa.Column('email', sa.String(length=255), nullable=False),
        sa.Column('created_at', sa.DateTime(), nullable=True),
        sa.PrimaryKeyConstraint('id'),
        sa.UniqueConstraint('email'),
        schema='master'
    )
    # ### end Alembic commands ###


def downgrade() -> None:
    """Downgrade schema."""
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_table('users', schema='master')
    # ### end Alembic commands ###

マイグレーションファイルの自動生成の仕組みを図にすると次のようなイメージになります。--autogenerate は「モデルにはあるけど DB にはないもの」を検出して、それを作成するマイグレーションを生成してくれます。

自動生成の注意点

公式のドキュメントにあるように --autogenerate オプションで検出できる変更とできないものがあることに注意が必要です。
そのため、データベースの構造の変更については、人間によるレビューを行うことが強く推奨されます。

検出できるもの

変更の種類 説明
テーブルの追加・削除 新しいテーブルの作成や既存テーブルの削除
カラムの追加・削除 テーブル内のカラムの追加や削除
カラムのnullable状態の変更 NULL許可/不許可の変更
インデックスの基本的な変更 明示的に名前をつけたインデックスやユニーク制約の変更
外部キー制約の基本的な変更 外部キーの追加・削除

オプションで検出できるもの(設定が必要)

変更の種類 設定
カラムの型の変更 compare_type=True(デフォルトで有効)
サーバーデフォルト値の変更 compare_server_default=True(デフォルトで無効)

検出できないもの

変更の種類 理由・対処法
テーブル名の変更 「削除+追加」として検出されるため、手動で名前変更に修正が必要
カラム名の変更 同上。「削除+追加」として検出される
名前のない制約 制約には必ず名前をつける(例:UniqueConstraint('col1', name="uq_col1")
一部の ENUM 型 ENUM をネイティブサポートしない DB では検出が困難

まとめ

この記事では、データベースのマイグレーションと ORMapper について、まずマイグレーションに着目して Alembic を例に説明しました。

マイグレーションツールを使うことで得られるメリットを改めて整理すると、次のようになります。

  • 変更履歴が残る:「いつ・なぜ・何を変えたか」がコードとして記録される
  • 安全に戻せるdowngrade で以前の状態に戻せるため、失敗を恐れずに変更できる
  • 環境を再現できる:開発環境・本番環境で同じ構造を確実に作れる
  • チーム開発に強い:Git などでマイグレーションファイルを共有すれば、全員が同じ DB 構造を持てる

また、SQLAlchemy のような ORMapper でモデルを定義することで、--autogenerate による自動生成も可能になります。
ただし、テーブル名やカラム名の変更など、検出できない変更もあるため、生成されたファイルは必ず目視で確認する習慣をつけることが大切です。

この記事がマイグレーションや ORMapper に初めて触れる方の理解の助けになれば幸いです。
私自身もまだまだ学ぶことが多いですが、同じように戸惑っている方の参考になればと思い、備忘も兼ねてまとめました。

6
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?