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 从公开仓库中收录这些内容。

举报或申请下架