SQLite'ta hazır bir şema göçü (migration) aracı yoktur; bunun yerine veritabanı dosyasının başlığında uygulamanın serbestçe kullanabileceği bir tamsayı bulunur. PRAGMA user_version bu sayıyı okur ve yazar. Yaygın düzen şudur: uygulama açılışta sürümü okur, kendisinden daha yeni her göç adımını sırayla tek bir işlem (transaction) içinde çalıştırır ve her adımın sonunda user_version değerini o adımın numarasına yükseltir. İşlem başarısız olursa hem şema değişikliği hem de sürüm numarası birlikte geri alınır.
Bu yazıda user_version, schema_version ve application_id arasındaki farkı, göç betiklerini komut satırında güvenle çalıştırmayı, ALTER TABLE'ın yetmediği durumlarda resmî 12 adımlı yöntemi ve sunucuda göç yaparken dikkat edilecek noktaları örneklerle ele alıyoruz. ALTER TABLE komutunun kendisi için SQLite ALTER TABLE yazısına, serinin tamamı için SQLite öğrenme rehberine bakabilirsiniz.
user_version, schema_version ve application_id

Her SQLite veritabanı dosyası 100 baytlık bir başlıkla başlar ve bu başlıkta üç ayrı tamsayı göç işleriyle ilgilidir. Üçü de PRAGMA ile okunur, ancak görevleri tamamen farklıdır ve birbirinin yerine kullanılmamalıdır.
| PRAGMA | Başlıktaki yeri | Kim yönetir | Ne için kullanılır |
|---|---|---|---|
| user_version | 60. bayt | Uygulama | Şema sürüm numarası gibi uygulamaya özgü bir değer; SQLite bu sayıyı kendisi hiç kullanmaz |
| schema_version | 40. bayt | SQLite | Şema her değiştiğinde SQLite tarafından otomatik artırılır; hazırlanmış ifadelerin eski şemayla çalışmasını önler |
| application_id | 68. bayt | Uygulama | Dosyanın hangi uygulamaya ait olduğunu belirten 32 bitlik işaretli tamsayı |
PRAGMA user_version;
-- çıktı: 0
PRAGMA schema_version;
-- çıktı: 0
CREATE TABLE kullanici(id INTEGER PRIMARY KEY, ad TEXT NOT NULL);
PRAGMA schema_version;
-- çıktı: 1
CREATE INDEX kullanici_ad ON kullanici(ad);
PRAGMA schema_version;
-- çıktı: 2
PRAGMA user_version = 1;
PRAGMA user_version;
-- çıktı: 1
PRAGMA schema_version;
-- çıktı: 2
SELECT user_version FROM pragma_user_version;
-- çıktı: 1
PRAGMA application_id = 1234;
PRAGMA application_id;
-- çıktı: 1234Çıktıda görüldüğü gibi yeni bir veritabanında iki değer de sıfırdır. Her CREATE ifadesi schema_version değerini bir artırdı; user_version ise yalnızca biz yazdığımızda değişti ve onu değiştirmek schema_version değerine dokunmadı. pragma_user_version tablo değerli fonksiyonu, aynı değeri sıradan bir SELECT içinde okumanızı sağlar; bu, sürüm kontrolünü başka bir sorguyla birleştirmek istediğinizde işe yarar. Tablo değerli pragma fonksiyonlarının genel kuralları için SQLite PRAGMA komutları yazısına bakın.
Değer 32 bitlik işaretli bir tamsayı olarak saklanır. Denememizde negatif bir sayı olduğu gibi saklandı, 'abc' gibi sayı olmayan bir metin verdiğimizde ise değer 0 oldu ve hiçbir hata mesajı çıkmadı. Bu sessiz davranış nedeniyle sürüm numarasını her zaman düz bir tamsayı olarak yazın ve yazdıktan sonra okuyarak doğrulayın.
Göç düzeni: numaralı adımlar ve tek işlem
En sade ve en yaygın yaklaşım, her şema değişikliğini numaralı ayrı bir dosyada ya da uygulama kodunda numaralı bir metin olarak tutmaktır. Uygulama veritabanını açtığında PRAGMA user_version ile mevcut sürümü öğrenir. Değer 1 ise ve uygulama 3 numaralı şemayı bekliyorsa, önce 2 numaralı adım, ardından 3 numaralı adım çalıştırılır. Sıfır değeri, hiç göç görmemiş yepyeni bir dosya anlamına gelir; bu durumda ilk adım genellikle tabloları sıfırdan kurar.
-- goc_002.sql: sürüm 1'den sürüm 2'ye
BEGIN IMMEDIATE;
ALTER TABLE urun ADD COLUMN stok INTEGER NOT NULL DEFAULT 0;
ALTER TABLE urun ADD COLUMN aciklama TEXT;
CREATE INDEX urun_stok ON urun(stok);
PRAGMA user_version = 2;
COMMIT;Bu dosyada üç ayrıntı önemlidir. Birincisi, bütün değişiklikler tek bir BEGIN ile COMMIT arasındadır; SQLite'ta CREATE, ALTER ve DROP gibi şema komutları da işlem içinde çalışır ve geri alınabilir. İkincisi, PRAGMA user_version = 2 satırı aynı işlemin parçasıdır; bu yüzden şema ve sürüm numarası ya birlikte kalıcı olur ya da birlikte geri alınır. Üçüncüsü, BEGIN IMMEDIATE kullanılmıştır: bu biçim yazma işlemini hemen başlatır ve başka bir bağlantı o sırada yazıyorsa göç daha ilk satırda SQLITE_BUSY ile durur, yarı yolda değil.
Sürüm numarasının gerçekten işlemle birlikte geri alındığını kendiniz görebilirsiniz:
PRAGMA user_version;
-- çıktı: 1
BEGIN;
ALTER TABLE urun ADD COLUMN stok INTEGER NOT NULL DEFAULT 0;
PRAGMA user_version = 2;
ROLLBACK;
PRAGMA user_version;
-- çıktı: 1
SELECT count(*) FROM pragma_table_info('urun');
-- çıktı: 3ROLLBACK sonrasında hem eklenen sütun kayboldu hem de sürüm 1'de kaldı. İşlemlerin ayrıntıları, DEFERRED ile IMMEDIATE arasındaki fark ve SAVEPOINT kullanımı için SQLite transaction ve SAVEPOINT yazısına bakın.
Komut satırında göç çalıştırırken -bail neden şart
Göç dosyalarını sqlite3 aracına yönlendirerek çalıştırmak pratik bir yöntemdir, ancak burada sinsi bir tuzak vardır: komut satırı aracı betik modunda bir hata gördüğünde varsayılan olarak durmaz, hatayı yazar ve sonraki satırdan devam eder. İşlem içindeki bir ifadenin hata vermesi işlemi kendiliğinden bitirmez; dolayısıyla dosyanın sonundaki COMMIT yine çalışır ve geri kalan değişiklikler kalıcı olur. Aşağıdaki dosyada ilk ALTER bilerek hatalıdır, çünkü NOT NULL bir sütun NULL dışında bir varsayılan değer olmadan eklenemez.
-- hatali_goc.sql (ilk ALTER bilerek hatalı)
BEGIN;
ALTER TABLE urun ADD COLUMN stok INTEGER NOT NULL;
ALTER TABLE urun ADD COLUMN aciklama TEXT;
PRAGMA user_version = 2;
COMMIT;# -bail olmadan: hata yazılır ama betik devam eder
sqlite3 app.db < hatali_goc.sql
# çıktı: Runtime error near line 3: Cannot add a NOT NULL column with default value NULL
sqlite3 app.db "PRAGMA user_version;"
# çıktı: 2 (sürüm 2 oldu ama stok sütunu yok, yalnızca aciklama eklendi)
# -bail ile (temiz bir kopya üzerinde): ilk hatada durur, açık işlem geri alınır
sqlite3 -bail app.db < hatali_goc.sql
# çıktı: Runtime error near line 3: Cannot add a NOT NULL column with default value NULL
echo $?
# çıktı: 1
sqlite3 app.db "PRAGMA user_version;"
# çıktı: 1-bail olmadan çalıştırıldığında veritabanı tutarsız bir hâle geldi: sürüm numarası 2 diyor ama 2 numaralı şemanın yarısı eksik. Uygulama bir sonraki açılışta bu adımı bir daha çalıştırmayacağı için hata kalıcı olur. -bail seçeneğiyle araç ilk hatada sıfırdan farklı bir çıkış koduyla kapandı. Bağlantı kapanırken açık kalan işlem geri alındığı için dosya göçten önceki hâliyle kaldı. Kabuk betiği veya dağıtım hattı içinde çıkış kodunu kontrol ederek sürecin devamına karar verebilirsiniz. Komut satırı seçeneklerinin tamamı için kurulum ve sqlite3 komut satırı ile nokta komutları yazılarına bakın.
ALTER TABLE ile yapılabilen göçler

Göçlerin çoğu küçük değişikliklerdir ve SQLite'ın doğrudan desteklediği dört ALTER TABLE biçimiyle çözülür: tabloyu yeniden adlandırmak, sütunu yeniden adlandırmak, sütun eklemek ve sütun silmek. SQLite şemayı sqlite_schema tablosunda CREATE ifadelerinin metni olarak sakladığı için yeniden adlandırma ve kısıtsız sütun ekleme yalnızca bu metni düzenler; tablo bir satır da içerse on milyon satır da içerse aynı hızda biter. Yeni sütun CHECK kısıtı taşıyorsa ya da sütun silinecekse SQLite mevcut satırları okumak veya yeniden yazmak zorundadır ve süre tablo boyutuyla orantılı olur.
ADD COLUMN ile eklenen sütunun bazı sınırları vardır ve göç yazarken en sık bunlara takılırsınız:
- Yeni sütun PRIMARY KEY veya UNIQUE olamaz; benzersizlik gerekiyorsa sütunu ekledikten sonra ayrı bir UNIQUE indeks oluşturun.
- Varsayılan değer CURRENT_TIME, CURRENT_DATE, CURRENT_TIMESTAMP ya da parantez içinde bir ifade olamaz.
- NOT NULL kısıtı varsa NULL dışında bir varsayılan değer verilmelidir; yukarıdaki hata tam olarak bu kuraldan geldi.
- Yabancı anahtar denetimi açıkken REFERENCES içeren bir sütunun varsayılanı NULL olmalıdır.
- STORED hesaplanan sütun eklenemez; VIRTUAL olanlar eklenebilir.
SQLite 3.53.0 sürümüyle ALTER TABLE, var olan bir sütuna NOT NULL ve CHECK kısıtı ekleyip kaldırabilecek biçimde genişletildi. Bu sürüm deneme makinemizdeki araçlardan yeni olduğu için örnek çalıştırmadık; söz dizimi ve sınırlar için ALTER TABLE yazısına ve SQLite 3.53 yenilikleri yazısına bakın. Uygulamanızla birlikte gelen SQLite kütüphanesi daha eskiyse bu komutlar söz dizimi hatası verir; göç dosyası yazmadan önce sqlite_version() ile sürümü kontrol edin.
Yeniden adlandırma sırasında bir ayrıntıya daha dikkat edin. 3.25.0 ve 3.26.0 sürümlerinden beri ALTER TABLE RENAME, tetikleyici ve görünüm gövdelerindeki ve yabancı anahtar tanımlarındaki tablo adlarını da günceller. PRAGMA legacy_alter_table=ON ise eski davranışa döndürür. Denemelerimizde bir derlemenin bu ayarı açık getirdiğini gördük:
PRAGMA legacy_alter_table;
-- çıktı: 1 (deneme makinemizdeki 3.50.6 komut satırı derlemesi)
-- çıktı: 0 (aynı sorgu SQLite 3.45.3 standart derlemesinde)Göç betiğinizin davranışı bu ayara bağlı olabileceğinden, kritik bir yeniden adlandırmadan önce ayarın değerini okuyun ve gerekiyorsa betiğin başında açıkça istediğiniz değere getirin.
ALTER TABLE yetmediğinde: 12 adımlı yöntem
Sütun tipini değiştirmek, var olan bir sütuna UNIQUE eklemek, birincil anahtarı değiştirmek veya sütunların sırasını düzenlemek gibi işler ALTER TABLE ile yapılamaz. SQLite belgeleri bunlar için genel bir yöntem tanımlar. Özetle: yabancı anahtar denetimini kapatın, işlem başlatın, tabloya bağlı indeks, tetikleyici ve görünümlerin SQL metnini kaydedin, istenen biçimde yeni bir tablo oluşturun, veriyi kopyalayın, eski tabloyu silin, yeni tabloyu eski adla yeniden adlandırın, indeks, tetikleyici ve görünümleri yeniden oluşturun, PRAGMA foreign_key_check ile ilişkileri denetleyin, işlemi onaylayın ve yabancı anahtar denetimini yeniden açın.
Aşağıdaki örnekte urun tablosundaki fiyat sütunu, NULL kabul eden düz bir tamsayıdan NOT NULL ve negatif olamayan bir sütuna dönüştürülüyor. Tabloya bir indeks, bir yabancı anahtar ve bir görünüm bağlı:
PRAGMA foreign_keys = OFF;
BEGIN;
SELECT type, name FROM sqlite_schema WHERE tbl_name = 'urun';
-- çıktı:
-- table|urun
-- index|urun_ad
CREATE TABLE urun_yeni(
id INTEGER PRIMARY KEY,
ad TEXT NOT NULL,
fiyat INTEGER NOT NULL DEFAULT 0 CHECK (fiyat >= 0)
);
INSERT INTO urun_yeni(id, ad, fiyat) SELECT id, ad, coalesce(fiyat, 0) FROM urun;
DROP TABLE urun;
ALTER TABLE urun_yeni RENAME TO urun;
CREATE INDEX urun_ad ON urun(ad);
PRAGMA foreign_key_check;
PRAGMA user_version = 3;
COMMIT;
PRAGMA foreign_keys = ON;
PRAGMA user_version;
-- çıktı: 3
SELECT * FROM pahali;
-- çıktı: Defter|40
PRAGMA integrity_check;
-- çıktı: okÜçüncü adımdaki sorgu dikkat çekici bir ayrıntıyı gösteriyor: pahali görünümü urun tablosunu kullandığı hâlde listede yok. Bunun nedeni, sqlite_schema tablosunda bir görünümün tbl_name değerinin görünümün kendi adı olmasıdır. Tabloyu kullanan görünümleri bulmak için sql sütununda tablo adını aramanız gerekir. Şema tablosunun sütunları için SQLite tabloları listeleme yazısına bakın.
Belgeler adımların sırası konusunda açık bir uyarı yapar: önce yeni tabloyu geçici bir adla oluşturup sonra eski adı vermek doğrudur; önce eski tabloyu yeniden adlandırıp sonra yeni tabloyu oluşturmak ise yanlıştır. Yanlış sıranın neden tehlikeli olduğunu şu deneme gösterir:
CREATE TABLE urun(id INTEGER PRIMARY KEY, ad TEXT NOT NULL, fiyat INTEGER);
CREATE TABLE siparis(id INTEGER PRIMARY KEY, urun_id INTEGER REFERENCES urun(id), adet INTEGER);
CREATE VIEW pahali AS SELECT ad, fiyat FROM urun WHERE fiyat > 20;
-- YANLIŞ sıra: önce eski tabloyu yeniden adlandırmak
ALTER TABLE urun RENAME TO urun_eski;
SELECT name, sql FROM sqlite_schema WHERE name IN ('siparis', 'pahali');
-- çıktı (SQLite 3.45.3, legacy_alter_table kapalı):
-- siparis|CREATE TABLE siparis(id INTEGER PRIMARY KEY, urun_id INTEGER REFERENCES "urun_eski"(id), adet INTEGER)
-- pahali|CREATE VIEW pahali AS SELECT ad, fiyat FROM "urun_eski" WHERE fiyat > 20Yeniden adlandırma, yabancı anahtarı ve görünümü urun_eski adına taşıdı. Ardından urun_eski silindiğinde bu nesneler var olmayan bir tabloya işaret eder. Doğru sırada ise yeniden adlandırılan tablo, yeni tablonun kendisidir ve diğer nesnelerdeki urun adları zaten doğru yeri gösterir.
İlk adımın neden işlem başlamadan önce yapıldığına da dikkat edin. Çok ifadeli bir işlemin ortasında PRAGMA foreign_keys değerini değiştirmek hata vermez, yalnızca hiçbir etkisi olmaz:
PRAGMA foreign_keys = ON;
BEGIN;
PRAGMA foreign_keys = OFF;
PRAGMA foreign_keys;
-- çıktı: 1
COMMIT;Yabancı anahtar eylemleri ve foreign_key_check çıktısının nasıl okunacağı için SQLite foreign key yazısına bakın.
schema_version değerine neden dokunmamalısınız
schema_version yazılabilir bir pragma olsa da göç numarası olarak kullanılmaz. SQLite her ifadeyi çalıştırırken bu değeri kontrol eder ve ifade hazırlandığından beri şemanın değişip değişmediğini buradan anlar. Değeri elle değiştirmek, ifadelerin eski bir şema bilgisiyle çalışmasına, yanlış sonuçlara ve veritabanı bozulmasına yol açabilir. Belgeler bu pragma için açıkça bozulma uyarısı verir ve savunma (defensive) kipi açık bağlantılarda değeri değiştirme girişimleri sessizce yok sayılır. Ayrıca VACUUM da bu sayı açısından bir şema değişikliği sayılır; yani değer, sizin göçlerinizle hiçbir ilgisi olmadan artabilir.
Belgelerde, diskteki veriyi etkilemeyen bazı değişiklikler için writable_schema ile sqlite_schema metnini doğrudan düzenleyen ve sonunda schema_version değerini bir artıran daha hızlı bir yöntem de anlatılır. Bu yöntemde yapılan tek bir yazım hatası veritabanını okunamaz hâle getirebilir; belgeler de önce ayrı bir deneme veritabanında sınamayı ve yedek almayı önerir. Sıradan uygulama göçlerinde 12 adımlı yöntem daha güvenlidir.
Sunucuda göç çalıştırmadan önce
Canlı bir sunucuda göç çalıştırmak, yerel makinede denemekten farklıdır. Önce bir yedek alın; çalışan bir veritabanını düz cp ile kopyalamak yerine .backup komutunu veya VACUUM INTO ifadesini kullanın. Ayrıntılar SQLite yedekleme yazısında. Göçü aynı dosyanın bir kopyası üzerinde deneyip çıktıyı kontrol etmek, canlı dosyada sürprizle karşılaşma ihtimalini azaltır.
Göç sırasında uygulamanın başka bağlantıları da veritabanına yazmaya çalışabilir. BEGIN IMMEDIATE göçün yazma kilidini en başta almasını sağlar; diğer bağlantıların bu sürede beklemesi için onlarda PRAGMA busy_timeout ayarlı olmalıdır. Uzun süren göçler, örneğin büyük bir tabloyu kopyalayan 12 adımlı işlemler, bu süre boyunca yazanları bekletir. Kilit hatalarının ayrıntıları için database is locked hatası yazısına bakın.
Sık yapılan hatalar

- Göç dosyasını
-bailolmadan çalıştırmak ve hata sonrasında COMMIT'in yine çalışmasına izin vermek. PRAGMA user_versionsatırını işlemin dışına, COMMIT'ten sonraya koymak; araya giren bir çökme sürümü şemayla uyumsuz bırakır.- Göç numarası olarak
schema_versionkullanmak veya bu değeri elle değiştirmek. - İşlemin ortasında
PRAGMA foreign_keys = OFFyazıp yabancı anahtarların kapandığını sanmak. - 12 adımlı yöntemde önce eski tabloyu yeniden adlandırmak.
- Yeni tabloyu kurduktan sonra eski tabloya bağlı indeks ve tetikleyicileri yeniden oluşturmayı unutmak; DROP TABLE bunları da siler.
- Uygulamadaki SQLite sürümünü kontrol etmeden yalnızca yeni sürümlerde bulunan bir ALTER TABLE biçimini kullanmak.
Sık Sorulan Sorular
user_version değerini kaç olarak başlatmalıyım?
Yeni oluşturulan her veritabanında değer 0'dır; ek bir şey yapmanız gerekmez. İlk göç adımınızı 1 olarak numaralandırırsanız, 0 değeri "henüz hiçbir tablo kurulmadı" anlamına gelir ve uygulama ilk açılışta şemayı sıfırdan kurar.
Göçleri geri almak (down migration) için SQLite bir şey sunuyor mu?
Hayır. SQLite yalnızca işlem içinde yapılan değişiklikleri ROLLBACK ile geri alabilir; onaylanmış bir göçü tersine çevirmek için ters işlemleri yapan ayrı bir göç adımı yazmanız gerekir. Geri dönüş planınızın temeli yine de göç öncesinde alınan yedek olmalıdır.
Birden fazla veritabanı dosyam varsa her biri için ayrı sürüm mü tutulur?
Evet. user_version her dosyanın kendi başlığında durur. ATTACH ile bağlanmış bir veritabanının değerini PRAGMA ekli_ad.user_version biçiminde okuyabilirsiniz. Bağlı veritabanlarıyla çalışma için SQLite ATTACH DATABASE yazısına bakın.
application_id ne zaman işe yarar?
Veritabanını uygulamanızın kendi dosya biçimi olarak kullanıyorsanız, açtığınız dosyanın gerçekten sizin uygulamanıza ait olduğunu bu sayıyla doğrulayabilirsiniz. Göç sürümüyle ilgisi yoktur; ikisini ayrı tutun.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: SQLite öğrenme rehberi · SQLite ALTER TABLE · SQLite transaction · SQLite PRAGMA komutları · SQLite foreign key · SQLite yedekleme