マイグレーション
スキーマ変更を SQL ファイルに記録して、各環境に順次適用する仕組み。 dev / deploy / push の使い分け、シャドウ DB、本番運用、破壊的変更まで。
マイグレーションフロー全体像
schema.prisma を編集
│
▼
prisma migrate dev (開発)
├─ シャドウ DB で SQL 検証
├─ migrations/yyyymmddhhmmss_name/migration.sql 生成
├─ DB に適用
└─ Client 再生成
│
▼
git commit migrations/ + schema.prisma
│
▼
本番デプロイ時
▼
prisma migrate deploy (本番)
└─ 未適用の migration を順次実行
主要コマンド
migrate dev(開発専用)
npx prisma migrate dev --name add_user_role
- schema 変更を検出
- シャドウ DB で SQL 検証
- 実際の DB に適用
- Prisma Client 再生成
- seed があれば実行
migrate deploy(本番専用)
npx prisma migrate deploy
- 未適用のマイグレーションを順番に実行
- 新規の migration を生成しない
- シャドウ DB は使わない
- CI/CD で実行
migrate reset(リセット)
npx prisma migrate reset
- DB を全消去
- 全 migration を最初から実行
- seed を実行
- 確認プロンプトあり
- 本番では絶対に実行しない
db push(プロトタイピング用)
npx prisma db push
- schema を DB に直接同期(migration ファイル作らず)
- 素早く試したいときに便利
- 変更履歴が残らない → 本番禁止
- 削除を伴う変更で警告
migration ディレクトリの中身
prisma/
├── schema.prisma
└── migrations/
├── 20260101120000_init/
│ └── migration.sql
├── 20260105140000_add_user_role/
│ └── migration.sql
└── migration_lock.toml # provider 情報
migration.sql の例
-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL,
"email" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
シャドウ DB
migrate dev は schema 変更を検証するためにもう 1 つの DBを使う。
- ローカル開発: 同じ Postgres インスタンス内で自動作成
- 権限が無い環境(Supabase / Neon の本番)では失敗
- クラウドで dev コマンドが必要ならshadowDatabaseUrl を指定
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
shadowDatabaseUrl = env("SHADOW_DATABASE_URL")
}
migrate dev / deploy の正しい使い分け
| 環境 | コマンド |
|---|---|
| ローカル開発 | migrate dev |
| CI(PR) | migrate diff で予習、migrate deploy でテスト用 DB に適用 |
| ステージング | migrate deploy |
| 本番 | migrate deploy |
db push と migrate の使い分け
- 初期プロトタイピング:
db pushで素早く試す - 本格開発に入ったら:
migrate devに切替 - 本番: 必ず migration ファイルベース
baseline(既存 DB を Prisma 管理に取り込む)
既存 DB を Prisma で管理し始めるとき、初期 migration を「すでに適用済み」とマークする手順:
# 1. 既存 DB から schema 取り込み
npx prisma db pull
# 2. 既存 DB の構造を SQL として保存
mkdir -p prisma/migrations/0_init
npx prisma migrate diff \
--from-empty \
--to-schema-datamodel prisma/schema.prisma \
--script > prisma/migrations/0_init/migration.sql
# 3. 適用済みとマーク
npx prisma migrate resolve --applied 0_init
migration の手作業編集
生成された SQL を手で書き換えても OK。Prisma の DSL で表現できないこと(複雑な制約、トリガー、データ移行)はここで書く。
# SQL 生成だけして適用しない
npx prisma migrate dev --create-only --name add_search_index
# 編集してから
npx prisma migrate dev
例: 全文検索インデックスを足す
-- prisma/migrations/.../migration.sql に追記
CREATE INDEX "Post_fts_idx" ON "Post" USING GIN (to_tsvector('simple', "title"));
破壊的変更の戦略(expand & contract)
本番ではいきなり DROP しない。3 段階でゼロダウンタイム移行する:
- Expand: 新カラムを追加(新旧両方が動く状態)
- Migrate data: 旧 → 新 にデータコピー
- Contract: 旧カラムを削除(アプリ更新後)
例: fullName を firstName + lastName に分割
-- ステップ 1: 新カラムを追加(NULL 許容)
ALTER TABLE "User" ADD COLUMN "firstName" TEXT;
ALTER TABLE "User" ADD COLUMN "lastName" TEXT;
-- ステップ 2: バックフィル(別 migration、別デプロイ)
UPDATE "User" SET
"firstName" = SPLIT_PART("fullName", ' ', 1),
"lastName" = SPLIT_PART("fullName", ' ', 2)
WHERE "firstName" IS NULL;
-- ステップ 3: NOT NULL + 旧カラム DROP(アプリ更新後の別 migration)
ALTER TABLE "User" ALTER COLUMN "firstName" SET NOT NULL;
ALTER TABLE "User" DROP COLUMN "fullName";
migration の差分プレビュー
npx prisma migrate diff \
--from-schema-datamodel prisma/schema.prisma \
--to-schema-datasource prisma/schema.prisma
現在の DB 状態と schema を比較して、差分 SQL を出力。CI で「予期しない変更」を検知するのに便利。
migration history が壊れたとき
resolve(マーク操作)
# 「適用済み」とマーク(手で SQL 流したあと)
npx prisma migrate resolve --applied "20260101_xxx"
# 「ロールバック済み」とマーク
npx prisma migrate resolve --rolled-back "20260101_xxx"
本番で壊れた場合
- 失敗した migration を手動で SQL 修正
resolve --appliedでマーク- schema と migration ファイルを揃える
- その後の migrations は通常通り
seed
npx prisma db seed
migrate reset 時にも自動で実行される。
seed スクリプトは package.json で登録(セットアップ 参照)。
CI / CD パイプライン例(GitHub Actions)
name: deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- run: npx prisma migrate deploy
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
- run: npx prisma generate
- run: npm run build
- run: ... # デプロイ
本番マイグレーションの安全 TIP
- 事前にステージングで実行して時間とロックを確認
- 大テーブルへの
ALTERは長時間ロックを引き起こす - Postgres は
CREATE INDEX CONCURRENTLYでロックなし作成可(ただし migration の自動 SQL では出ないので手書き) NOT NULL追加は既存行が違反すると失敗 → デフォルト値必須- 大量更新を含む migration は別の運用スクリプトに分離
- ロールバック前提の戦略を持つ(前向きの再 migration で対応)
prisma db pull の使い所
- 既存 DB を Prisma に取り込む(baseline)
- 外部チームが DDL を直接書いたあとの同期
- 取り込んだあとは schema.prisma を整形して、以降は migrate dev に戻す
migration の命名
--name は意味のある名前に。タイムスタンプは自動で付く。
- ○
add_user_role - ○
create_indexes_on_posts - ×
fix/update
失敗パターン
| 症状 | 原因 / 対処 |
|---|---|
| Drift detected | DB と migration history がズレた。migrate resolve で整合 |
| shadow DB 失敗 | 権限不足。SHADOW_DATABASE_URL 指定 |
| 本番で migrate dev 実行 | 事故。即停止、migrate deploy へ |
| NOT NULL 追加失敗 | 既存に NULL がある。デフォルト値 / 段階的移行 |
| FK エラー | 関連テーブルの順序、CASCADE 設定 |
| 長時間ロック | CONCURRENTLY、夜間メンテ、pg_repack |
本番事故を避ける鉄則
1) ローカルでテスト / 2) ステージングで本番同等の data 量で時間計測 / 3) 本番デプロイ前にバックアップ / 4) ロックの可能性がある変更は低トラフィック時間帯に / 5) 失敗時のロールバック手順を事前に用意。