Database Migrations

affaan-m/ECC/docs/tr/skills/database-migrations

作者 affaan-mef648e01899ba3e8dc6371642deaaf64b4477775無授權條款275K 個星標收錄於 2026年10月9日更新於 2026年10月9日儲存庫4 天前更新

Şema değişiklikleri, veri migration'ları, rollback'ler ve PostgreSQL, MySQL ve yaygın ORM'ler (Prisma, Drizzle, Django, TypeORM, golang-migrate) arasında sıfır kesinti deployment'ları için veritabanı migration en iyi uygulamaları.

AI 產生的概覽

指導 PostgreSQL、MySQL 及常見 ORM 的安全、可回復的資料庫結構與資料遷移。

功能
提供資料庫遷移的最佳實務指引,包括安全檢查清單、PostgreSQL 新增欄位與索引的模式、分批資料回填,以及零停機 expand-contract 策略。也記錄了 Prisma、Drizzle、Django、TypeORM 與 golang-migrate 的工作流程與範例結構,並附有反模式對照表。產出為書面的遷移方案、SQL 片段與 ORM 指令序列,本身不執行任何操作。
適用情境
適用於建立或修改資料表、新增或移除欄位或索引、執行資料回填或轉換、規劃零停機結構變更,或為新專案建置遷移工具時。
執行需求
無需指令碼或執行階段相依性,僅為說明性文件。實際套用時需具備資料庫存取權限,以及相應的 ORM 或遷移命令列工具(Prisma、Drizzle、Django、TypeORM、golang-migrate)。

Veritabanı Migration Kalıpları

Üretim sistemleri için güvenli, geri alınabilir veritabanı şema değişiklikleri.

Ne Zaman Aktifleştirmeli

  • Veritabanı tabloları oluştururken veya değiştirirken
  • Sütun veya indeks eklerken/kaldırırken
  • Veri migration'ları çalıştırırken (backfill, dönüştürme)
  • Sıfır kesinti şema değişiklikleri planlarken
  • Yeni bir proje için migration araçları kurarken

Temel İlkeler

  1. Her değişiklik bir migration'dır — üretim veritabanlarını asla manuel olarak değiştirmeyin
  2. Migration'lar üretimde sadece ileri — rollback'ler yeni forward migration'lar kullanır
  3. Şema ve veri migration'ları ayrıdır — tek migration'da DDL ve DML'yi asla karıştırmayın
  4. Migration'ları üretim boyutundaki veriye karşı test edin — 100 satırda çalışan migration 10M'de kilitlenebilir
  5. Migration'lar üretimde çalıştıktan sonra değişmezdir — üretimde çalışan migration'ı asla düzenlemeyin

Migration Güvenlik Kontrol Listesi

Herhangi bir migration uygulamadan önce:

  • Migration UP ve DOWN'a sahip (veya açıkça geri alınamaz olarak işaretlenmiş)
  • Büyük tablolarda tam tablo kilitleri yok (concurrent operasyonlar kullan)
  • Yeni sütunlar varsayılanlara sahip veya nullable (varsayılan olmadan NOT NULL asla ekleme)
  • İndeksler concurrent oluşturuluyor (mevcut tablolar için CREATE TABLE ile inline değil)
  • Veri backfill şema değişikliğinden ayrı bir migration
  • Üretim verisinin kopyasına karşı test edilmiş
  • Rollback planı dokümante edilmiş

PostgreSQL Kalıpları

Güvenli Sütun Ekleme

sql
-- İYİ: Nullable sütun, kilit yokALTER TABLE users ADD COLUMN avatar_url TEXT;
-- İYİ: Varsayılanlı sütun (Postgres 11+ anlık, yeniden yazma yok)ALTER TABLE users ADD COLUMN is_active BOOLEAN NOT NULL DEFAULT true;
-- KÖTÜ: Mevcut tabloda varsayılansız NOT NULL (tam yeniden yazma gerektirir)ALTER TABLE users ADD COLUMN role TEXT NOT NULL;-- Bu tabloyu kilitler ve her satırı yeniden yazar

Kesinti Olmadan İndeks Ekleme

sql
-- KÖTÜ: Büyük tablolarda yazmaları engellerCREATE INDEX idx_users_email ON users (email);
-- İYİ: Engellemez, concurrent yazmalara izin verirCREATE INDEX CONCURRENTLY idx_users_email ON users (email);
-- Not: CONCURRENTLY transaction bloğu içinde çalıştırılamaz-- Çoğu migration aracı bunun için özel işleme ihtiyaç duyar

Sütun Yeniden Adlandırma (Sıfır Kesinti)

Üretimde asla doğrudan yeniden adlandırmayın. Expand-contract kalıbını kullanın:

sql
-- Adım 1: Yeni sütun ekle (migration 001)ALTER TABLE users ADD COLUMN display_name TEXT;
-- Adım 2: Veriyi backfill et (migration 002, veri migration'ı)UPDATE users SET display_name = username WHERE display_name IS NULL;
-- Adım 3: Uygulama kodunu her iki sütunu okuma/yazma için güncelle-- Uygulama değişikliklerini deploy et
-- Adım 4: Eski sütuna yazmayı durdur, kaldır (migration 003)ALTER TABLE users DROP COLUMN username;

Güvenli Sütun Kaldırma

sql
-- Adım 1: Sütuna tüm uygulama referanslarını kaldır-- Adım 2: Sütun referansı olmadan uygulamayı deploy et-- Adım 3: Sonraki migration'da sütunu kaldırALTER TABLE orders DROP COLUMN legacy_status;
-- Django için: SeparateDatabaseAndState kullanarak modelden kaldır-- DROP COLUMN oluşturmadan (sonra sonraki migration'da kaldır)

Büyük Veri Migration'ları

sql
-- KÖTÜ: Tüm satırları tek transaction'da günceller (tabloyu kilitler)UPDATE users SET normalized_email = LOWER(email);
-- İYİ: İlerleme ile batch güncellemeDO $$DECLARE  batch_size INT := 10000;  rows_updated INT;BEGIN  LOOP    UPDATE users    SET normalized_email = LOWER(email)    WHERE id IN (      SELECT id FROM users      WHERE normalized_email IS NULL      LIMIT batch_size      FOR UPDATE SKIP LOCKED    );    GET DIAGNOSTICS rows_updated = ROW_COUNT;    RAISE NOTICE 'Updated % rows', rows_updated;    EXIT WHEN rows_updated = 0;    COMMIT;  END LOOP;END $$;

Prisma (TypeScript/Node.js)

İş Akışı

bash
# Şema değişikliklerinden migration oluşturnpx prisma migrate dev --name add_user_avatar
# Üretimde bekleyen migration'ları uygulanpx prisma migrate deploy
# Veritabanını sıfırla (sadece dev)npx prisma migrate reset
# Şema değişikliklerinden sonra client oluşturnpx prisma generate

Şema Örneği

prisma
model User {  id        String   @id @default(cuid())  email     String   @unique  name      String?  avatarUrl String?  @map("avatar_url")  createdAt DateTime @default(now()) @map("created_at")  updatedAt DateTime @updatedAt @map("updated_at")  orders    Order[]
  @@map("users")  @@index([email])}

Özel SQL Migration

Prisma'nın ifade edemediği operasyonlar için (concurrent indeksler, veri backfill'leri):

bash
# Boş migration oluştur, sonra SQL'i manuel düzenlenpx prisma migrate dev --create-only --name add_email_index
sql
-- migrations/20240115_add_email_index/migration.sql-- Prisma CONCURRENTLY oluşturamaz, bu yüzden manuel yazıyoruzCREATE INDEX CONCURRENTLY IF NOT EXISTS idx_users_email ON users (email);

Drizzle (TypeScript/Node.js)

İş Akışı

bash
# Şema değişikliklerinden migration oluşturnpx drizzle-kit generate
# Migration'ları uygulanpx drizzle-kit migrate
# Şemayı doğrudan push et (sadece dev, migration dosyası yok)npx drizzle-kit push

Şema Örneği

typescript
import { pgTable, text, timestamp, uuid, boolean } from "drizzle-orm/pg-core";
export const users = pgTable("users", {  id: uuid("id").primaryKey().defaultRandom(),  email: text("email").notNull().unique(),  name: text("name"),  isActive: boolean("is_active").notNull().default(true),  createdAt: timestamp("created_at").notNull().defaultNow(),  updatedAt: timestamp("updated_at").notNull().defaultNow(),});

Django (Python)

İş Akışı

bash
# Model değişikliklerinden migration oluşturpython manage.py makemigrations
# Migration'ları uygulapython manage.py migrate
# Migration durumunu gösterpython manage.py showmigrations
# Özel SQL için boş migration oluşturpython manage.py makemigrations --empty app_name -n description

Veri Migration

python
from django.db import migrations
def backfill_display_names(apps, schema_editor):    User = apps.get_model("accounts", "User")    batch_size = 5000    users = User.objects.filter(display_name="")    while users.exists():        batch = list(users[:batch_size])        for user in batch:            user.display_name = user.username        User.objects.bulk_update(batch, ["display_name"], batch_size=batch_size)
def reverse_backfill(apps, schema_editor):    pass  # Veri migration'ı, geri alma gerekmez
class Migration(migrations.Migration):    dependencies = [("accounts", "0015_add_display_name")]
    operations = [        migrations.RunPython(backfill_display_names, reverse_backfill),    ]

golang-migrate (Go)

İş Akışı

bash
# Migration çifti oluşturmigrate create -ext sql -dir migrations -seq add_user_avatar
# Tüm bekleyen migration'ları uygulamigrate -path migrations -database "$DATABASE_URL" up
# Son migration'ı rollback etmigrate -path migrations -database "$DATABASE_URL" down 1
# Versiyonu zorla (dirty durumu düzelt)migrate -path migrations -database "$DATABASE_URL" force VERSION

Migration Dosyaları

sql
-- migrations/000003_add_user_avatar.up.sqlALTER TABLE users ADD COLUMN avatar_url TEXT;CREATE INDEX CONCURRENTLY idx_users_avatar ON users (avatar_url) WHERE avatar_url IS NOT NULL;
-- migrations/000003_add_user_avatar.down.sqlDROP INDEX IF EXISTS idx_users_avatar;ALTER TABLE users DROP COLUMN IF EXISTS avatar_url;

Sıfır Kesinti Migration Stratejisi

Kritik üretim değişiklikleri için expand-contract kalıbını takip edin:

Faz 1: EXPAND  - Yeni sütun/tablo ekle (nullable veya varsayılanlı)  - Deploy: uygulama hem ESKİ hem YENİ'ye yazar  - Mevcut veriyi backfill et
Faz 2: MIGRATE  - Deploy: uygulama YENİ'den okur, her İKİSİNE yazar  - Veri tutarlılığını doğrula
Faz 3: CONTRACT  - Deploy: uygulama sadece YENİ'yi kullanır  - Eski sütun/tabloyu ayrı migration'da kaldır

Zaman Çizelgesi Örneği

Gün 1: Migration new_status sütunu ekler (nullable)Gün 1: App v2 deploy et — hem status hem new_status'a yazGün 2: Mevcut satırlar için backfill migration'ı çalıştırGün 3: App v3 deploy et — sadece new_status'tan okurGün 7: Migration eski status sütununu kaldırır

Anti-Kalıplar

Anti-KalıpNeden Başarısız OlurDaha İyi Yaklaşım
Üretimde manuel SQLDenetim izi yok, tekrarlanamazHer zaman migration dosyaları kullan
Deploy edilmiş migration'ları düzenlemeOrtamlar arası sapma yaratırBunun yerine yeni migration oluştur
Varsayılansız NOT NULLTabloyu kilitler, tüm satırları yeniden yazarNullable ekle, backfill et, sonra kısıt ekle
Büyük tabloda inline indeksBuild sırasında yazmaları engellerCREATE INDEX CONCURRENTLY
Tek migration'da şema + veriRollback zor, uzun transaction'larAyrı migration'lar
Kodu kaldırmadan önce sütun kaldırmaEksik sütunda uygulama hatalarıÖnce kodu kaldır, sonra sütunu sonraki deploy'da kaldır

來源與署名

來源:affaan-m/ECC位於docs/tr/skills/database-migrations提交ef648e0

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架