Mert Özen Her Satırda Daha İleri
Veritabanı SQL Server Derinlemesine — #24

Şema Değişikliğini Yönetmek: SQL Database Projects ve DACPAC (State-Based) ile DbUp ve Flyway (Migration-Based)

Mert Özen 15 Kas 2026 12 dk 10 görüntülenme
Şema Değişikliğini Yönetmek: SQL Database Projects ve DACPAC (State-Based) ile DbUp ve Flyway (Migration-Based)

State-based araç rename'i bilmez: projede kolon adını değiştirince SqlPackage kolonu silip yeniden eklemeye çalıştı ve yalnız BlockOnPossibleDataLoss sayesinde durdu; refactorlog ile sp_rename üretti. Microsoft.Build.Sql 2.3.0 ile DACPAC, deploy script ve DeployReport, varsayılan olarak silinen elle eklenmiş index, yanlış fark üreten CHECK tanımı, DbUp'ın fark etmediği ve Flyway'in checksum ile yakaladığı değiştirilmiş migration.

Sürüm notu: Bu yazıdaki örnekler SQL Server 2025 CU10 (17.0.5035.5) üzerinde; .NET 10 SDK, Microsoft.Build.Sql 2.3.0, SqlPackage 170.5.96, dbup-sqlserver 7.2.0 (Microsoft.Data.SqlClient 7.1.2 ile) ve Docker'da Flyway OSS (açık kaynak) Edition 13.12.2 (Apache 2.0 lisanslı) ile çalıştırılmıştır.

Önce Tahmin Et: Bir Kolonun Adını Değiştirmek

Sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) raporlama veritabanı Entity Framework Core (EF Core) kullanmıyor; tabloları 22. yazıdaki gece job'ının doldurduğu DailySalesReport ve aylık hedefler. Şeması bir SQL Database Project'te duruyor ve her deploy'da SqlPackage ile yayınlanıyor. Finans ekibi Revenue kolonunun adının NetRevenue olmasını istedi. Projedeki tablo dosyasında tek satırı değiştirdim (view'daki referansı da), projeyi build edip her zamanki gibi yayınladım. Tabloda dört satır veri var:

    NetRevenue decimal(14,2) NOT NULL,
sqlpackage /Action:Publish /SourceFile:bin/Debug/Bookstore.Reporting.Db.dacpac /TargetServerName:"localhost,14333" /TargetDatabaseName:sqls_24_reporting /TargetUser:sa /TargetPassword:"$MSSQL_SA_PASSWORD" /TargetTrustServerCertificate:True

SqlPackage kolonun adını mı değiştirir, yoksa başka bir şey mi yapar?

Publishing to database 'sqls_24_reporting' on server 'localhost,14333'.
Initializing deployment (Start)
*** The column [dbo].[DailySalesReport].[Revenue] is being dropped, data loss could occur.
*** The column [dbo].[DailySalesReport].[NetRevenue] on table [dbo].[DailySalesReport] must be added, but the column has no default value and does not allow NULL values. If the table contains data, the ALTER script will not work. To avoid this issue you must either: add a default value to the column, mark it as allowing NULL values, or enable the generation of smart-defaults as a deployment option.
...
Updating database (Failed)
*** Could not deploy package.
Error SQL72014: Core Microsoft SqlClient Data Provider: Msg 50000, Level 16, State 127, Line 8 Rows were detected. The schema update is terminating because data loss might occur.

Başka bir şey yapmaya çalışıyor: Revenue'yu silip NetRevenue'yu yeni kolon olarak eklemek. Proje yalnız hedef şemayı tarif eder; "bu kolon eskisinin yeni adı" bilgisi o şemada yok. Veriyi kurtaran şey, SqlPackage'ın varsayılan olarak açık olan BlockOnPossibleDataLoss ayarı oldu: tabloda satır görünce deploy'u durdurdu. Bu yazıda şema değişikliğini yönetmenin iki yolunu, state-based (SQL Database Project ve onun ürettiği DACPAC (şemanın derlenmiş paket dosyası)) ve migration-based (DbUp ve Flyway) yaklaşımı aynı Bookstore senaryolarında çalıştırıp karşılaştırıyoruz.

İki Yaklaşım: Hedef Şema mı, Sıralı Adımlar mı?

State-based yaklaşımda kaynak kontrolündeki şey veritabanının olması gereken hâlidir: her tablo, view ve procedure bir CREATE script'i. Bir araç bu tarifi hedef veritabanıyla karşılaştırır ve aradaki farkı kapatan script'i kendisi üretir. Migration-based yaklaşımda kaynak kontrolündeki şey değişikliklerin kendisidir: sıra numaralı script'ler. Araç, hangi script'lerin uygulandığını veritabanındaki bir journal tablosunda tutar ve yalnız eksik olanları çalıştırır. EF Core Migrations ikinci gruptandır; script'leri C# modelinden üretir (EF Core serisinin migrations yazıları).

24-state-vs-migration-tr.png

SQL Database Project: dotnet build ile DACPAC

SDK-style SQL Database Project, Microsoft.Build.Sql SDK'sıyla normal bir .NET projesi gibi dotnet build ile derlenir; Visual Studio, Visual Studio Code'un SQL Database Projects eklentisi ya da yalnız komut satırı yeter. Proje dosyası kısa:

<Project Sdk="Microsoft.Build.Sql/2.3.0">
  <PropertyGroup>
    <Name>Bookstore.Reporting.Db</Name>
    <DSP>Microsoft.Data.Tools.Schema.Sql.Sql170DatabaseSchemaProvider</DSP>
    <ModelCollation>1033, CI</ModelCollation>
  </PropertyGroup>
</Project>

DSP hedef platformdur; Sql170 SQL Server 2025. Klasördeki bütün .sql dosyaları projeye kendiliğinden dahil olur. Tablolar sıradan CREATE script'leri:

CREATE TABLE dbo.DailySalesReport
(
    ReportDate date NOT NULL,
    Category varchar(30) NOT NULL,
    Orders int NOT NULL,
    Items int NOT NULL,
    Revenue decimal(14,2) NOT NULL,
    CreatedAt datetime2(0) NOT NULL CONSTRAINT DF_DailySalesReport_CreatedAt DEFAULT SYSUTCDATETIME(),
    CONSTRAINT PK_DailySalesReport PRIMARY KEY (ReportDate, Category)
);

CREATE TABLE dbo.SalesTarget
(
    TargetMonth date NOT NULL,
    Category varchar(30) NOT NULL,
    TargetRevenue decimal(14,2) NOT NULL,
    CONSTRAINT PK_SalesTarget PRIMARY KEY (TargetMonth, Category),
    CONSTRAINT CK_SalesTarget_FirstDay CHECK (DATEPART(DAY, [TargetMonth]) = (1))
);

Build, bütün nesneleri derleyip birbirine referanslarını doğrular (view'da yanlış yazılmış bir kolon adı derleme hatasıdır) ve sonucu tek bir DACPAC dosyasına yazar. İlk yayın veritabanını da oluşturur; aşağıdaki çıktı boş bir deneme veritabanına yapılan yayından:

  Bookstore.Reporting.Db -> /home/user/bookstore/db/Bookstore.Reporting.Db/bin/Debug/Bookstore.Reporting.Db.dacpac
Build succeeded.
Publishing to database 'sqls_24_v1check' on server 'localhost,14333'.
...
Creating database sqls_24_v1check...
Creating Table [dbo].[DailySalesReport]...
Creating Table [dbo].[SalesTarget]...
Creating Default Constraint [dbo].[DF_DailySalesReport_CreatedAt]...
Creating Check Constraint [dbo].[CK_SalesTarget_FirstDay]...
Creating View [dbo].[MonthlySalesVsTarget]...
Update complete.
Successfully published database.

Rename, Refactorlog ve Deploy Script'ini Okumak

Açılıştaki sorunun çözümü refactorlog: projedeki rename işlemlerinin kaydı. Visual Studio'da kolonu Rename ile değiştirdiğinde bu dosya kendiliğinden oluşur; komut satırında çalışıyorsan elle yazarsın ve proje dosyasına eklersin:

<Operations Version="1.0" xmlns="http://schemas.microsoft.com/sqlserver/dac/Serialization/2012/02">
  <Operation Name="Rename Refactor" Key="5d4e3c2b-1a09-4f8e-9d7c-6b5a4f3e2d1c" ChangeDateTime="11/14/2026 21:00:00">
    <Property Name="ElementName" Value="[dbo].[DailySalesReport].[Revenue]" />
    <Property Name="ElementType" Value="SqlSimpleColumn" />
    <Property Name="ParentElementName" Value="[dbo].[DailySalesReport]" />
    <Property Name="ParentElementType" Value="SqlTable" />
    <Property Name="NewName" Value="[NetRevenue]" />
  </Operation>
</Operations>

<ItemGroup>
  <RefactorLog Include="Bookstore.Reporting.Db.refactorlog" />
</ItemGroup>

Yayınlamadan önce SqlPackage'a ne yapacağını sordum. /Action:Script veritabanına dokunmadan deploy script'ini üretir:

sqlpackage /Action:Script /SourceFile:bin/Debug/Bookstore.Reporting.Db.dacpac /TargetServerName:"localhost,14333" /TargetDatabaseName:sqls_24_reporting /TargetUser:sa /TargetPassword:"$MSSQL_SA_PASSWORD" /TargetTrustServerCertificate:True /OutputPath:deploy.sql
PRINT N'Rename [dbo].[DailySalesReport].[Revenue] to NetRevenue';
GO
EXECUTE sp_rename @objname = N'[dbo].[DailySalesReport].[Revenue]', @newname = N'NetRevenue', @objtype = N'COLUMN';
GO
PRINT N'Altering View [dbo].[MonthlySalesVsTarget]...';
GO
ALTER VIEW dbo.MonthlySalesVsTarget
...

Artık DROP/ADD değil sp_rename. Script ayrıca veritabanında dbo.__RefactorLog tablosunu oluşturup işlemin anahtarını oraya yazıyor; bir sonraki yayında aynı rename tekrar denenmez. Yayın başarılı oldu ve dört satırın değerleri NetRevenue kolonunda duruyor. CI/CD'de bu adım bir kural olmalı: pipeline DACPAC'ten üretilen script'i bir artifact olarak saklar, bir insan okur, yayın ondan sonra yapılır. State-based aracın gücü farkı kendisinin hesaplaması; zayıflığı da bu, çünkü ne üreteceğini okumadan bilemezsin.

Drift: Elle Eklenen Index ve Yanlış Fark

Bir gece raporlama yavaşladı ve nöbetçi DBA (veritabanı yöneticisi), üretimde elle bir index ekledi. Proje bundan habersiz. Bir sonraki yayının ne yapacağını /Action:DeployReport gösteriyor:

CREATE INDEX IX_DailySalesReport_Category ON dbo.DailySalesReport (Category) INCLUDE (NetRevenue);
<DeploymentReport xmlns="http://schemas.microsoft.com/sqlserver/dac/DeployReport/2012/02"><Alerts /><Operations><Operation Name="Drop"><Item Value="[dbo].[DailySalesReport].[IX_DailySalesReport_Category]" Type="SqlIndex" /></Operation></Operations></DeploymentReport>

Varsayılan ayarlarla. SqlPackage'ın projede olmayan nesneleri silmesi (DropObjectsNotInSource) varsayılan olarak kapalıdır, ama index'ler için ayrı bir ayar var ve o (DropIndexesNotInSource) varsayılan olarak açıktır. Gece eklenen index bir sonraki deploy'da sessizce gider ve rapor yeniden yavaşlar. Doğru çözüm index'i projeye eklemek; projeden ve veritabanından hangisinin doğru olduğuna her seferinde karar vermek zorunda kalmamak için üretimde elle değişiklik yapılmaz, yapıldıysa hemen projeye taşınır.

İkinci drift kaynağı yanlış farktır. Projenin ilk hâlinde check constraint'i CHECK (DAY(TargetMonth) = 1) diye yazmıştım. Hiçbir şey değiştirmeden aldığım rapor:

<DeploymentReport xmlns="http://schemas.microsoft.com/sqlserver/dac/DeployReport/2012/02"><Alerts /><Operations><Operation Name="Drop"><Item Value="[dbo].[CK_SalesTarget_FirstDay]" Type="SqlCheckConstraint" /></Operation><Operation Name="Create"><Item Value="[dbo].[CK_SalesTarget_FirstDay]" Type="SqlCheckConstraint" /></Operation></Operations></DeploymentReport>

SQL Server constraint'i (datepart(day,[TargetMonth])=(1)) olarak saklıyor ve SqlPackage iki ifadeyi farklı buluyor; her yayında constraint düşürülüp yeniden oluşturuluyor ve tablo baştan kontrol ediliyor. Tanımı SQL Server'ın sakladığı biçime yakın yazınca (DATEPART(DAY, [TargetMonth]) = (1)) rapor boş döndü. Her yayında aynı nesneyi değiştiren bir rapor görüyorsan bu tür bir yanlış farka bakıyorsundur.

DbUp: Dapper Kullanan Bir Servis için Migration'lar

Bookstore'un fiyat servisi (23. yazıdaki yayınevi listelerini işleyen servis) Dapper kullanıyor ve kendi küçük veritabanı var. EF Core modeli olmadığı için EF Core Migrations'ın sağladığı otomatik script üretimi burada işe yaramaz; şema az ve değişiklikler küçük. DbUp bu durum için iyi bir seçenek: script'leri sen yazarsın, DbUp sırayla çalıştırır ve bir journal tablosuna kaydeder. Migration'lar ayrı bir konsol uygulamasında, script'ler embedded resource olarak duruyor:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="dbup-sqlserver" Version="7.2.0" />
    <PackageReference Include="Microsoft.Data.SqlClient" Version="7.1.2" />
  </ItemGroup>

  <ItemGroup>
    <EmbeddedResource Include="Scripts\*.sql" />
  </ItemGroup>

</Project>

Microsoft.Data.SqlClient'ı doğrudan eklememin nedeni, dbup-sqlserver 7.2.0'ın getirdiği 6.1.4 sürümünün build'de bilinen güvenlik açığı uyarısı (NU1902, NU1903) veren bir bağımlılık çekmesi; 7.1.2 ile uyarı kalkıyor ve migration'lar sorunsuz çalışıyor.

using System.Reflection;
using DbUp;
using DbUp.Engine;

string connectionString = Environment.GetEnvironmentVariable("BOOKSTORE_PRICES_DB")
    ?? throw new InvalidOperationException("Set the BOOKSTORE_PRICES_DB environment variable.");

EnsureDatabase.For.SqlDatabase(connectionString);

UpgradeEngine upgrader = DeployChanges.To
    .SqlDatabase(connectionString)
    .WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly())
    .WithTransactionPerScript()
    .LogToConsole()
    .Build();

DatabaseUpgradeResult result = upgrader.PerformUpgrade();
if (!result.Successful)
{
    Console.Error.WriteLine(result.Error);
    return 1;
}

Console.WriteLine($"Applied scripts: {result.Scripts.Count()}");
return 0;
CREATE TABLE dbo.PriceHistory
(
    PriceHistoryId bigint IDENTITY(1,1) NOT NULL CONSTRAINT PK_PriceHistory PRIMARY KEY,
    Isbn varchar(13) NOT NULL,
    Price decimal(10,2) NOT NULL,
    Currency char(3) NOT NULL,
    ValidFrom date NOT NULL,
    Source varchar(50) NOT NULL
);

CREATE INDEX IX_PriceHistory_Isbn_ValidFrom ON dbo.PriceHistory (Isbn, ValidFrom DESC) INCLUDE (Price, Currency);

İlki Scripts/0001_create_price_history.sql, ikincisi Scripts/0002_add_price_history_isbn_index.sql. DbUp script'leri adlarına göre sıralar; sıfırla doldurulmuş numaralar bu yüzden. İlk ve ikinci çalıştırma:

2026-11-15 00:28:48 +00:00 [INF] Created database sqls_24_prices
2026-11-15 00:28:49 +00:00 [INF] Beginning database upgrade
2026-11-15 00:28:49 +00:00 [INF] Checking whether journal table exists
2026-11-15 00:28:49 +00:00 [INF] Journal table does not exist
2026-11-15 00:28:49 +00:00 [INF] Executing Database Server script 'Bookstore.PriceService.Migrations.Scripts.0001_create_price_history.sql'
2026-11-15 00:28:49 +00:00 [INF] Checking whether journal table exists
2026-11-15 00:28:49 +00:00 [INF] Creating the [SchemaVersions] table
2026-11-15 00:28:49 +00:00 [INF] The [SchemaVersions] table has been created
2026-11-15 00:28:49 +00:00 [INF] Executing Database Server script 'Bookstore.PriceService.Migrations.Scripts.0002_add_price_history_isbn_index.sql'
2026-11-15 00:28:49 +00:00 [INF] Upgrade successful
Applied scripts: 2

2026-11-15 00:28:49 +00:00 [INF] Beginning database upgrade
2026-11-15 00:28:49 +00:00 [INF] Checking whether journal table exists
2026-11-15 00:28:49 +00:00 [INF] Fetching list of already executed scripts.
2026-11-15 00:28:49 +00:00 [INF] No new scripts need to be executed - completing.
Applied scripts: 0

Şimdi tuzak: bir geliştirici PriceHistory'ye CreatedAt kolonu gerektiğini fark ediyor ve en kolay yeri seçiyor, zaten uygulanmış 0001 script'ine kolonu ekliyor. Build, çalıştırma ve kontrol:

2026-11-15 00:28:50 +00:00 [INF] No new scripts need to be executed - completing.
Applied scripts: 0
SELECT ScriptName, Applied FROM dbo.SchemaVersions ORDER BY Id;
SELECT COL_LENGTH('dbo.PriceHistory', 'CreatedAt') AS created_at_length;
ScriptName                                                                       Applied
-------------------------------------------------------------------------------  -----------------------
Bookstore.PriceService.Migrations.Scripts.0001_create_price_history.sql          2026-11-15 00:28:49.067
Bookstore.PriceService.Migrations.Scripts.0002_add_price_history_isbn_index.sql  2026-11-15 00:28:49.087

created_at_length
-----------------
NULL

DbUp journal'da yalnız script'in adını tutar, içeriğini değil. Değişikliği fark etmedi; kolon bu veritabanında yok, ama sıfırdan kurulan bir test veritabanında var. İki ortamın şeması sessizce ayrıştı. Doğru yol uygulanmış script'e dokunmamak ve değişikliği yeni bir script olarak eklemek:

ALTER TABLE dbo.PriceHistory
    ADD CreatedAt datetime2(0) NOT NULL CONSTRAINT DF_PriceHistory_CreatedAt DEFAULT SYSUTCDATETIME();
2026-11-15 00:28:52 +00:00 [INF] Executing Database Server script 'Bookstore.PriceService.Migrations.Scripts.0003_add_price_history_created_at.sql'
2026-11-15 00:28:52 +00:00 [INF] Upgrade successful
Applied scripts: 1

Flyway: Aynı Migration'lar, Checksum ile

Flyway aynı fikrin Java tabanlı, dilden bağımsız bir uygulaması ve CI'da ya da .NET olmayan ekiplerde yaygın. Açık kaynak sürümünü (Flyway OSS Edition, Apache 2.0) resmî Docker image'ıyla çalıştırdım. Dosya adları sözleşmenin parçası: V1__create_price_history.sql, V2__add_price_history_isbn_index.sql sıralı migration'lar; R__latest_price_view.sql ise repeatable migration, içeriği her değiştiğinde yeniden çalışır (view ve procedure'ler için). Bağlantı bilgisi bir flyway.toml'da, parola ortam değişkeninde:

[environments.dev]
url = "jdbc:sqlserver://host.docker.internal:14333;databaseName=sqls_24_flyway;encrypt=true;trustServerCertificate=true"
user = "sa"

[flyway]
environment = "dev"
locations = ["filesystem:sql"]
docker run --rm -e FLYWAY_PASSWORD -v "$PWD:/flyway/project" -w /flyway/project flyway/flyway:13.12.2 migrate
Flyway OSS Edition 13.12.2 by Redgate
...
Database: jdbc:sqlserver:******** (Microsoft SQL Server 17.0)
Schema history table [sqls_24_flyway].[dbo].[flyway_schema_history] does not exist yet
Successfully validated 3 migrations (execution time 00:00.032s)
Creating Schema History table [sqls_24_flyway].[dbo].[flyway_schema_history] ...
Current version of schema [dbo]: << Empty Schema >>
Migrating schema [dbo] to version "1 - create price history"
Migrating schema [dbo] to version "2 - add price history isbn index"
Migrating schema [dbo] with repeatable migration "latest price view"
Successfully applied 3 migrations to schema [dbo], now at version v2 (execution time 00:00.025s)

Aynı tuzağı Flyway'de denedim: uygulanmış V1'de Source kolonunu varchar(100) yaptım ve migrate dedim:

ERROR: Validate failed: Migrations have failed validation
Migration checksum mismatch for migration version 1
-> Applied to database : -1537186180
-> Resolved locally    : 473330901
Either revert the changes to the migration, or run repair to update the schema history.

DbUp'ın sessiz kaldığı yerde Flyway, uygulanan her script'in checksum'ını flyway_schema_history'de tuttuğu için durdu. Değişikliği geri alıp V3__add_price_history_created_at.sql'i ekledim ve view'a yeni kolonu ekleyen değişikliği R__ dosyasında yaptım:

Successfully validated 5 migrations (execution time 00:00.028s)
Current version of schema [dbo]: 2
Migrating schema [dbo] to version "3 - add price history created at"
Migrating schema [dbo] with repeatable migration "latest price view"
Successfully applied 2 migrations to schema [dbo], now at version v3 (execution time 00:00.042s)

Hangisi Ne Zaman?

SoruSQL Database Project + DACPACDbUpFlywayEF Core Migrations
Kaynakta ne varHedef şemaSıralı script'lerSıralı ve repeatable script'lerC# model ve üretilen migration'lar
Script'i kim yazarAraç üretirSenSenAraç üretir, sen düzeltirsin
RenameRefactorlog gerekirAçıkça sp_renameAçıkça sp_renameRenameColumn, kontrol gerekir
Uygulanmış script değişirseBöyle bir kavram yok; fark her yayında yeniden hesaplanırFark etmezChecksum hatasıFark etmez; geçmiş tablosunda yalnız migration adı var
En uygun yerÇok nesneli, DBA'li veritabanları, raporlamaDapper ile çalışan .NET servisleriBirden çok dil ve veritabanı olan ekiplerEF Core kullanan uygulamalar

Kural basit: bir veritabanını tek bir araç yönetir. Bookstore'da sipariş veritabanı EF Core Migrations'la (EF Core serisinin 7. ve "Migration'ları Pratikte" yazıları), raporlama veritabanı SQL Database Project'le, fiyat servisi DbUp'la yönetiliyor. Aynı veritabanında EF Core Migrations ile SqlPackage'ı birlikte çalıştırmak, birinin eklediğini ötekinin silmeye çalışması demek; drift bölümündeki index örneği bunun küçük bir hâli.

Hangi araç olursa olsun iki ilke değişmiyor. Script'ler tekrar çalıştırılmaya dayanıklı olmalı (SQL Temelleri serisinin günlük DDL (şema tanımlama komutları) ve idempotent script'ler yazısı); DbUp ve Flyway bir script'i bir kez çalıştırır ama yarıda kalan bir script'i elle tekrar çalıştırman gerekebilir. Ve uygulama yeni sürüme geçerken eski sürüm de bir süre aynı veritabanıyla çalışacağı için değişiklikler geriye uyumlu yapılır: kolonu önce ekle, uygulamayı geçir, eskisini sonra kaldır. Açılıştaki rename üretimde tek adımda yapılmamalıydı; bu expand-contract yaklaşımı Deployment Stratejileri serisinin "Migration'ların Deploy'daki Yeri" yazısında.

SQL Server 2019/2022'de farkı: araçların davranışı sunucu sürümünden bağımsız; değişen hedef platform: projede DSP 2019 için Sql150, 2022 için Sql160. SqlPackage, hedef sunucu proje platformundan eskiyse yayını reddeder.

Üretimde Dikkat

Pipeline'da yayından önce script üret ve sakla (/Action:Script, Flyway'de info). Üretime giden her şema değişikliğinin okunabilir bir kaydı olur.

BlockOnPossibleDataLoss'u kapatma. Açılıştaki deploy'u durduran tek şey oydu.

Migration'ları uygulamanın açılışında değil, deploy'un ayrı bir adımında çalıştır. Birden çok instance aynı anda açılınca aynı migration'ı birlikte denerler; ayrıca uygulamanın kullanıcısına şema değiştirme yetkisi vermek gerekir.

Sık Düşülen Tuzaklar

State-based yayında rename'in drop ve add'e dönüşmesi. Refactorlog kullan ve script'i okumadan yayınlama.

Aynı veritabanını iki araçla yönetmek. Biri diğerinin değişikliğini drift sayar.

Uygulanmış bir migration script'ini sonradan değiştirmek. DbUp sessizce atlar, Flyway durur; ikisinde de doğru yol yeni bir script.

Üretimde elle yapılan değişikliği projeye taşımamak. DropIndexesNotInSource varsayılan olarak açık olduğu için o index bir sonraki deploy'da gider.

Küçük Bir Deneme

★☆☆ Raporlama projesine elle eklenen index'i Indexes/IX_DailySalesReport_Category.sql olarak ekle ve DeployReport'un boş döndüğünü doğrula.

★★☆ Fiyat servisinin Source kolonunu SourceName yapmayı DbUp ile expand-contract adımlarına böl: yeni kolon, veri kopyalama, uygulamanın geçişi, eski kolonun kaldırılması. Kaç script ve kaç deploy gerekiyor?

★★★ Flyway'de flyway repair komutunun checksum uyuşmazlığında ne yaptığını dene. Hangi durumda kullanmak doğru, hangisinde tehlikeli?

SQL Server Derinlemesine serisini bu yazıyla bitiriyoruz. Motorun içinden, veri page'lerden, index'lerden, optimizer'dan ve lock'lardan sonra bu son blokta bir geliştiricinin günlük işletimde karşılaştığı işlere geçtik: backup ve restore, bakım, Agent job'ları, veri ve şema taşıma. Tek sunucunun ötesindeki konular, yani replication, high availability, partitioning ve sharding, Veritabanı Performansı ve Scaling serisinde.