---
title: "Alembic（アレンビック）とは？SQLAlchemyのマイグレーションの使い方とautogenerateの落とし穴【1.20対応】"
url: "https://www.issoh.co.jp/tech/details/3417/"
published: 2024-08-29
updated: 2026-09-26
categories: ["データベース"]
publisher: "株式会社一創"
---

# Alembic（アレンビック）とは？SQLAlchemyのマイグレーションの使い方とautogenerateの落とし穴【1.20対応】

**Alembic（アレンビック）**は、PythonのORMであるSQLAlchemyのために作られたデータベースマイグレーションツールです。テーブルや列の変更をPythonのスクリプトとして残し、`alembic upgrade` で適用、`alembic downgrade` で巻き戻します。作者はSQLAlchemyと同じMike Bayerで、ライセンスはMITです。

2026年9月11日公開の最新版1.20.0から、SQLAlchemy 1.4のサポートが外れ、SQLAlchemy 2.0以上とPython 3.10以上が必須になりました。この記事では、1.20.0・SQLAlchemy 2.1.1・SQLite 3.53.1の環境で実際にコマンドを流した結果をもとに、基本の使い方と、自動生成（autogenerate）をそのまま適用するとデータが消える場面を解説します。なお、Sony Pictures ImageworksとIndustrial Light & Magicが公開した3DCGのシーン交換形式（拡張子 .abc）も同じ「Alembic」という名前ですが、この記事で扱うのはPythonのツールです。

## まとめ：Alembicの要点

- AlembicはSQLAlchemyのモデル定義と実際のDBを比べ、差分をマイグレーションスクリプトとして生成・適用するツール。DBの現在のリビジョンは `alembic_version` テーブルに記録され、複数の分岐先まで適用されている場合は複数行になる
- 基本の流れは `alembic init` → env.pyに `target_metadata` を設定 → `alembic revision --autogenerate` → 生成ファイルを読む → `alembic upgrade head`
- 1.20.0（2026-09-11）でSQLAlchemy 1.4対応を終了。1.4のまま運用するプロジェクトは `alembic<1.20` に固定する
- autogenerateは列名・テーブル名の変更を検出できず「追加＋削除」として出力する。NULL許可の列ならエラーも出ずに値が消えるため、`op.alter_column(..., new_column_name=...)` に書き換える
- SQLiteは列の型変更をALTERで実行できない（NOT NULLの変更は3.53.0から対応）。`batch_alter_table` を使い、失敗したら元テーブルとデータが残っていることを確かめたうえで、作業用の `_alembic_tmp_` テーブルだけを削除して再実行する
- 本番では `--sql` で実行予定のSQLをレビューし、CIでは `alembic check`（差分があると終了コード255）でモデルとマイグレーションのずれを止める

まず、SQLAlchemy本体とAlembicがそれぞれ何を担当するかを整理します。

## Alembicとは：SQLAlchemy公式のマイグレーションツール

Alembicのソースコードは、GitHubのSQLAlchemy組織（sqlalchemy/alembic）で管理されています。読みは「アレンビック」で、英単語のalembicは蒸留器（ランビキ）を指します。

マイグレーションツールが解決するのは、「どのDBに、どこまでのスキーマ変更が当たっているか」をコードで管理する問題です。Alembicは変更ごとに1ファイル（リビジョン）を作り、各ファイルが直前のリビジョンID（`down_revision`）を持つことで変更履歴をチェーン状につなぎます。DB側には `alembic_version` テーブルが作られ、現在のリビジョンIDが記録されます。履歴の先端が一つなら1行、適用済みの分岐先が複数あれば複数行になります。開発機・ステージング・本番のどれでも、`alembic upgrade head` を流せば最新のスキーマにそろいます。

### SQLAlchemy・Flask-Migrate・Djangoとの関係

SQLAlchemy本体はテーブル定義やクエリの発行を担い、既存テーブルの変更は扱いません（`create_all()` は存在しないテーブルを作るだけです）。その変更部分を受け持つのがAlembicです。ORM自体の書き方は[SQLAlchemyの基本と使い方](/tech/details/7912/)、ORMという仕組み全般は[ORMの仕組みとメリット・デメリット](/tech/details/3755/)で解説しています。

FlaskのFlask-Migrateは、内部でAlembicを呼び出す拡張です。`flask db migrate` の実体はAlembicのautogenerateなので、この記事の注意点がそのまま当てはまります。SQLModelのモデルもSQLAlchemyのMetaDataを持つため、env.pyで `SQLModel.metadata` を渡せばAlembicで管理できます。

Djangoは独自のマイグレーション機能を持ち、Alembicは使いません。比較の基準も異なり、Djangoの `makemigrations` は過去のマイグレーションファイルから復元した状態とモデルを比べます。Alembicのautogenerateは、接続した実際のDBとモデルを比べます。そのため、未適用のリビジョンが残ったDBで `alembic revision --autogenerate` を実行すると、`Target database is not up to date.` で止まります。Django側の流れは[Django migrateの実務](/tech/details/17080/)を参照してください。

## Alembic 1.20の対応環境と押さえるべき変更点

PyPIの1.20.0は `Requires-Python >=3.10`、依存は `SQLAlchemy>=2.0` とテンプレートエンジンのMakoです。直近の変更のうち、既存プロジェクトの動作に影響するものを[公式の変更履歴（changelog）](https://alembic.sqlalchemy.org/en/latest/changelog.html)から抜き出しました。

| 版      | 公開日        | 変更点                           |
| ------ | ---------- | ----------------------------- |
| 1.20.0 | 2026-09-11 | SQLAlchemy 1.4対応を終了（2.0が最低要件） |
| 1.19.2 | 2026-09-04 | 名前付きCHECK制約の検出を既定で無効に戻す       |
| 1.19.0 | 2026-08-04 | 名前付きCHECK制約の追加・削除を検出          |
| 1.16.0 | 2025-05-21 | pyproject.tomlでの設定に対応         |
| 1.12.0 | 2023-08-31 | 型変更の検出（compare\_type）を既定で有効に  |
| 1.9.0  | 2022-12-15 | alembic check コマンドを追加         |

1.20.0の変更履歴には、SQLAlchemy 1.4がPython 3.13以上に対応していないことが削除の理由として書かれています。まだ1.4を使っているなら、Alembicを上げる前にSQLAlchemyを2.0へ移行する必要があります。CHECK制約の検出は1.19.0で既定の動作になりましたが、誤検出のおそれがあるため1.19.2で既定から外れました。1.19.0と1.19.1だけ挙動が違う点に注意してください。

## インストールとalembic initで作られるファイル

Alembicは、プロジェクトごとの仮想環境に入れるのが公式ドキュメントの推奨です。使うDBのドライバ（PostgreSQLならpsycopgなど）は別途インストールします。

```
python -m venv .venv
. .venv/bin/activate
pip install alembic
alembic init migrations
```

`alembic init` は、`alembic.ini` と `migrations/` ディレクトリ（env.py・script.py.mako・versions/）を作ります。`alembic list_templates` で選べるテンプレートは1.20.0で5種類です。

| テンプレート           | 用途                   |
| ---------------- | -------------------- |
| generic          | 既定。単一DB・同期ドライバ       |
| async            | asyncpgなど非同期ドライバ     |
| multidb          | 複数DBを1つの履歴で管理        |
| pyproject        | 設定をpyproject.tomlへ置く |
| pyproject\_async | pyproject＋非同期ドライバ    |

`alembic init -t pyproject migrations` を実行すると、`pyproject.toml` に `[tool.alembic]` セクションが書かれ、`script_location` などソースコード側の設定はそこへ移ります。ただし `alembic.ini` も同時に作られ、DBの接続URL（`sqlalchemy.url`）とログ設定はこちらに残ります。接続先は環境ごとに変わり、ソースコードの設定とは性質が違うためです。

### env.pyのtarget\_metadata設定

autogenerateを使うには、env.pyの `target_metadata = None` をモデルのMetaDataに書き換えます。モデルを読み込まないままMetaDataが空になると、AlembicはDBにあるテーブルを削除対象として検出します。検証では、既存の `users` テーブルに対して `op.drop_table('users')` が生成されました。`Base.metadata` を渡していても、モデルクラスを定義したモジュールをimportしていなければ同じ状態になります。`target_metadata` が `None` のままなら、`--autogenerate` はMetaDataが渡されていないというエラーで止まります。

```
# migrations/env.py
import os
from models import Base   # モデルを定義したモジュールを必ずimportする

target_metadata = Base.metadata

# 接続URLは環境変数から渡す（alembic.iniに本番の認証情報を書かない）
config.set_main_option("sqlalchemy.url", os.environ["DATABASE_URL"].replace("%", "%%"))
```

`set_main_option` はConfigParserの変数展開を通るため、URLエンコードされたパスワードに含まれる `%` は `%%` にしないとエラーになります（Alembicの `config.py` のdocstringに明記）。

## Alembicの使い方：revision・upgrade・downgradeの流れ

次のモデルをmodels.pyに保存し、Unix系シェルで `export DATABASE_URL='sqlite:///app.db'` を実行してから、1本目のマイグレーションを作ります。

```
# models.py
from sqlalchemy import String
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column

class Base(DeclarativeBase):
    pass

class User(Base):
    __tablename__ = "users"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str] = mapped_column(String(50))
```

`alembic revision --autogenerate -m "create users"` を実行すると、`Detected added table 'users'` と表示され、versions/にファイルが1つ生成されました。中身は次のとおりです。

```
revision: str = '04fcf8a2cc0f'
down_revision: Union[str, Sequence[str], None] = None

def upgrade() -> None:
    op.create_table('users',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('name', sa.String(length=50), nullable=False),
    sa.PrimaryKeyConstraint('id')
    )

def downgrade() -> None:
    op.drop_table('users')
```

`Mapped[str]` と書いた列は `nullable=False`（NOT NULL）になります。NULLを許可したい列は `Mapped[str | None]` と書きます。生成されたファイルは「候補」なので、公式ドキュメントも適用前に必ず読んで直すよう求めています。

確認できたら `alembic upgrade head` で適用します。主なコマンドは次のとおりです。

| コマンド                     | 動作           |
| ------------------------ | ------------ |
| `alembic upgrade head`   | 最新まで適用       |
| `alembic upgrade +1`     | 1つだけ進める      |
| `alembic downgrade -1`   | 1つ戻す         |
| `alembic downgrade base` | すべて戻す        |
| `alembic current`        | DBの現在のリビジョン  |
| `alembic history`        | リビジョンの一覧     |
| `alembic heads`          | 末端のリビジョン     |
| `alembic stamp head`     | SQLを流さず版だけ記録 |

適用後の `alembic current` は `04fcf8a2cc0f (head)` を返し、`alembic_version` テーブルにも同じIDが1行入っていました。`stamp` は、Alembic導入前から存在するDBに「ここまでは適用済み」と記録するときに使います。

### downgrade（ロールバック）で戻る範囲

`alembic downgrade -1` は、単一の履歴では、現在適用されているリビジョンの `downgrade()` 関数を実行し、その親リビジョンへ戻ります。戻るのはスキーマで、データは戻りません。`downgrade()` が `drop_column` を含むなら、その列の値は消えます。`downgrade base` を実行すると、1本目の `drop_table` まで流れてテーブルごと消えます。検証では、base まで戻した後も `alembic_version` テーブル自体は残り、中身だけが空になりました。本番で戻す可能性があるリビジョンは、downgradeの動作をステージングで一度流してから出すのが安全です。

## autogenerateが検出できない変更と、データが消える場面

[公式ドキュメントのautogenerateの章](https://alembic.sqlalchemy.org/en/latest/autogenerate.html)は、「autogenerateは完璧を意図していない」と明記し、検出できる変更と検出できない変更を分けて示しています。

| 変更                     | 検出              |
| ---------------------- | --------------- |
| テーブル・列の追加と削除           | される             |
| NULL制約の変更              | される             |
| インデックス・名前付きUNIQUE・外部キー | される（基本的な変更）     |
| 列の型変更                  | される（1.12.0から既定） |
| サーバー側デフォルト値の変更         | 既定では無効          |
| 名前付きCHECK制約            | 既定では無効          |
| テーブル名・列名の変更            | されない（追加＋削除になる）  |
| 名前の無い制約                | されない            |

### 列名変更の誤検出とデータ消失

先ほどの `name` 列を `full_name` に改名し、Userモデルへ `email: Mapped[str | None] = mapped_column(String(255))` も追加してautogenerateを実行すると、次の3行が生成されました。

```
op.add_column('users', sa.Column('full_name', sa.String(length=50), nullable=False))
op.add_column('users', sa.Column('email', sa.String(length=255), nullable=True))
op.drop_column('users', 'name')
```

2行入ったテーブルにこれを適用すると、SQLiteは `Cannot add a NOT NULL column with default value NULL` で止まりました。止まったのは、新しい列がNOT NULLだったからです。NULL許可の列で同じ改名をした場合はエラーが出ず、既存の値 `taro` が `None` に変わった状態で適用が完了しました。追加された列は空で、元の列は削除されるためです。

改名は、生成されたコードを次のように書き換えます。この形に直したところ、2行の値は `full_name` 列にそのまま残り、`alembic check` も差分なしを返しました。

```
def upgrade() -> None:
    op.add_column("users", sa.Column("email", sa.String(255), nullable=True))
    op.alter_column("users", "name", new_column_name="full_name")

def downgrade() -> None:
    op.drop_column("users", "email")
    op.alter_column("users", "full_name", new_column_name="name")
```

レビューでは、同じテーブルに `add_column` と `drop_column` が並んでいたら改名を疑ってください。autogenerateの結果を読まずに `upgrade` する運用は、この事故に直結します。

### デフォルト値の変更と名前の無い制約

列の長さを `String(50)` から `String(100)` に変え、同時に `server_default` を追加して実行すると、検出されたのは型の変更だけでした。サーバー側のデフォルト値まで比較するには、env.pyの `context.configure()` に `compare_server_default=True` を渡します。公式ドキュメントは、単純なケース以外では正確にならないことがあるとして、先に対象のスキーマで試すよう求めています。

`DROP CONSTRAINT` には名前が必要なため、制約名が `None` のままでは通常の `drop_constraint` で削除対象を指定できません。既存の制約は、DB側で自動的に付いた名前を調べて指定するか、SQLiteならbatch modeの `naming_convention` 引数で名前を与えて削除します。1.20.0からは、名前が `None` の `drop_constraint` を生成すると警告コメントが入ります。MetaDataに `naming_convention` を設定し、制約名が必ず付くようにしておくのが根本的な対策です。

## SQLiteでALTERが通らないときのbatch mode

SQLiteはALTER文の対応が限られています。列の長さを `String(255)` から `String(500)` に変える `op.alter_column` をそのまま適用すると、`near "TYPE": syntax error` で失敗しました。SQLiteには列の型を変えるALTER構文が無いためです。一方、NOT NULL制約の追加・削除（`ALTER COLUMN ... SET NOT NULL`）はSQLite 3.53.0（2026-04-09）で対応しました。検証環境の3.53.1では、既存のNULLを埋めた後ならbatchを使わずに通りました。Pythonの `sqlite3` モジュールが使うSQLiteの版は環境によって違うため、`sqlite3.sqlite_version` で確認してください。

ALTERで表現できない変更に対して、Alembicは新しい構造の一時テーブルを作ってデータを写し、元のテーブルと差し替える「move and copy」方式の `batch_alter_table` を用意しています（[公式ドキュメントのbatch migrationsの章](https://alembic.sqlalchemy.org/en/latest/batch.html)）。次は `email` 列をNOT NULLにする例で、古いSQLiteでも動きます。

```
def upgrade() -> None:
    op.execute("UPDATE users SET email = '' WHERE email IS NULL")
    with op.batch_alter_table("users") as batch_op:
        batch_op.alter_column("email", existing_type=sa.String(255),
                              nullable=False, server_default="")
```

env.pyの `context.configure()` に `render_as_batch=True` を渡すと、autogenerateが最初からこの `with op.batch_alter_table(...)` の形で出力します。SQLiteを使うなら、この設定を入れておくのが無難です。

### batchが失敗した後に残る一時テーブル

上のコードから `UPDATE` 行を外し、`email` がNULLの行が残ったまま適用すると、`NOT NULL constraint failed: _alembic_tmp_users.email` で失敗しました。`server_default` は新しく入る行の既定値で、写してくる既存行のNULLは埋めません。

問題はその後です。失敗後のDBには `_alembic_tmp_users` テーブルが残り、`alembic_version` は1つ前のリビジョンのままでした。この状態では、`UPDATE` を足して再実行しても `table _alembic_tmp_users already exists` で止まります。SQLiteでは、AlembicがDDLを非トランザクションとして扱うためです（実行ログに `Will assume non-transactional DDL` と出ます）。`DROP TABLE _alembic_tmp_users` で一時テーブルを消してから再実行すると、適用が完了し、2行とも `email` に空文字が入りました。

batch modeは、`--sql`（オフラインモード）とも相性がよくありません。SQLiteに対して `--sql` で出力しようとすると、テーブル構造を読み取るためにDB接続が必要だというエラーで止まります。回避するには、`batch_alter_table` の `copy_from` にTableオブジェクトを渡します。

## チーム開発で起きる複数headとalembic merge

2人が同じリビジョンから別々にマイグレーションを作ると、履歴が枝分かれします。検証で同じ親から2本作ったところ、`alembic heads` が2行を返し、`alembic upgrade head` は次のエラーで止まりました。

```
FAILED: Multiple head revisions are present for given argument 'head';
please specify a specific target revision, '<branchname>@head' to narrow
to a specific head, or 'heads' for all heads
```

`alembic merge heads -m "merge posts and tags"` を実行すると、`down_revision` に2つの親をタプルで持つ合流用のリビジョンが生成され、`upgrade head` で2本と合流リビジョンが順に適用されました。

どちらのリビジョンもまだどの環境にも適用していないなら、後から作った側の `down_revision` をもう一方のIDに書き換えて一直線にすると、履歴を読みやすく保てます。どちらか一方でも共有環境に適用済みなら、既存の履歴は書き換えずmergeで合流させます（[公式ドキュメントのbranchesの章](https://alembic.sqlalchemy.org/en/latest/branches.html)）。CIで `alembic heads` の行数が1であることを確かめておくと、枝分かれしたままのマージを防げます。

## 本番運用：オフラインモードのSQLレビューとCIでのalembic check

DBに直接接続できない本番環境や、DBAの承認が必要な現場では、`--sql` を付けると実行する代わりにSQLを出力します。範囲は `開始:終了` で指定します。改名のリビジョンを出力した結果が次です。

```
$ alembic upgrade 04fcf8a2cc0f:b3a60b421b56 --sql
-- Running upgrade 04fcf8a2cc0f -> b3a60b421b56
ALTER TABLE users RENAME COLUMN name TO full_name;
ALTER TABLE users ADD COLUMN email VARCHAR(255);
UPDATE alembic_version SET version_num='b3a60b421b56' WHERE alembic_version.version_num = '04fcf8a2cc0f';
```

出力には `alembic_version` の更新も含まれるので、このSQLを手で流せば版の記録もそろいます。レビューでは `DROP` を含む行を最初に確認します。

`alembic check` は、autogenerateと同じ比較を行い、新しいリビジョンが必要な差分があれば失敗します。検証では、差分がないとき終了コード0、モデルだけを変えてリビジョンを作っていないとき（`New upgrade operations detected`）と、DBが最新まで適用されていないとき（`Target database is not up to date.`）はどちらも終了コード255でした。CIで空のDBに `alembic upgrade head` を流した後に `alembic check` を実行すれば、「モデルは変えたがリビジョンを作り忘れた」プルリクエストを止められます。ただし、checkが見るのはautogenerateが検出できる範囲だけです。改名は追加・削除の差分として検出されますが、改名という意図までは判断できません。サーバー側デフォルト値の比較は既定で無効です。

FastAPIでのディレクトリ構成やテストDBとの組み合わせは[FastAPIのベストプラクティス](/tech/details/4347/)で扱っています。Ruby側で宣言的にスキーマを管理する別の方式は、[Ridgepoleの使い方](/tech/details/5805/)で比較できます。

## よくある質問

### Alembicの読み方は？

「アレンビック」と読みます。英単語のalembicは蒸留器（ランビキ）の意味です。3DCGで使うAlembic形式（拡張子 .abc）も同じ読みですが、別のプロジェクトです。

### AlembicとSQLAlchemyの違いは？

SQLAlchemyはテーブル定義とクエリ発行を担うORM・SQLツールキットで、既存テーブルの変更は扱いません。Alembicはその変更履歴をリビジョンとして管理し、適用と巻き戻しを行います。Alembicを使うにはSQLAlchemyが必要で、1.20.0からはSQLAlchemy 2.0以上が必須です。

### alembic downgradeで全部戻すには？

`alembic downgrade base` を実行すると、すべてのリビジョンの `downgrade()` が逆順に流れます。戻るのはスキーマだけで、削除された列やテーブルのデータは戻りません。1つだけ戻すなら `alembic downgrade -1` です。

### autogenerateで空のマイグレーションが生成されるのはなぜ？

モデルが正しく読み込まれていて、DBがすでにモデルと一致していれば、空のファイルが生成されます。これは異常ではありません。注意したいのは逆のケースで、モデルのモジュールをimportし忘れてMetaDataが空になると、空のファイルではなく既存テーブルを消す `drop_table` が生成されます。`target_metadata` が `None` のままなら、ファイルは作られずエラーで止まります。

### alembic upgrade headで「Multiple head revisions」と出たら？

履歴が枝分かれしています。`alembic heads` で末端を確認し、`alembic merge heads` で合流用のリビジョンを作ってから `upgrade head` を実行します。どちらも未適用なら、片方の `down_revision` を書き換えて一直線にする方法もあります。

## 関連記事

- [SQLAlchemyとは？Python ORMの基本と使い方を入門から実践まで解説](/tech/details/7912/)
- [FastAPIのベストプラクティス｜SQLAlchemyでのディレクトリ構成・モデル設計・テスト](/tech/details/4347/)
- [SQLModelとは？SQLAlchemyとPydanticを統合したPython製ORMの特徴と使い方](/tech/details/3598/)
- [Django migrateの実務｜makemigrationsからの流れと本番適用・巻き戻し](/tech/details/17080/)
- [ORMとは？O/Rマッパーの仕組み・メリットとデメリットをSQL比較と実行結果で解説](/tech/details/3755/)

---

出典: [Alembic（アレンビック）とは？SQLAlchemyのマイグレーションの使い方とautogenerateの落とし穴【1.20対応】](<https://www.issoh.co.jp/tech/details/3417/>)（株式会社一創）
