SQLite'ta ATTACH DATABASE 'dosya.db' AS ad; komutu, açık bağlantıya ikinci bir veritabanı dosyası ekler. Bundan sonra o dosyadaki tablolara ad.tablo biçiminde erişir, iki dosya arasında tek bir SQL ifadesiyle birleştirme yapabilir ya da satır kopyalayabilirsiniz. İşiniz bittiğinde DETACH DATABASE ad; ile dosyayı bağlantıdan ayırırsınız.
Bu yazıda ATTACH ile arşiv dosyasına satır taşımayı, iki dosyayı tek sorguda birleştirmeyi, şema adı vermeden yazılan tablo adlarının nasıl çözüldüğünü, bağlantı başına dosya sınırını, işlemlerin birden çok dosyada ne zaman atomik olduğunu, kalıcı görünümlerin kısıtını ve güvenlik notlarını örneklerle anlatıyoruz. Örnekler SQLite 3.50.6 komut satırı aracıyla dosya üzerinde çalıştırıldı.
ATTACH ve şema adları

Belgelere göre AS anahtar kelimesinden önceki ifade dosya adını verir ve dosya adı, bağlantı açılırken kullanılan dosya adlarıyla aynı kurallara uyar. AS sonrasındaki ad ise SQLite'ın içeride kullandığı şema adıdır. Her bağlantıda iki şema zaten vardır: main açılan ana dosyayı, temp ise geçici tablo ve görünümlerin tutulduğu veritabanını temsil eder. Bu ikisi bağlanamaz ve ayrılamaz. Bağlanan dosya mevcut değilse boş bir veritabanı olarak oluşturulur.
-- sqlite3 magaza.db ile açılmış oturumda
CREATE TABLE siparis(id INTEGER PRIMARY KEY, musteri TEXT, tutar REAL, tarih TEXT);
INSERT INTO siparis VALUES
(1, 'Ayşe', 250, '2025-11-03'), (2, 'Mehmet', 120, '2025-12-19'),
(3, 'Zeynep', 480, '2026-02-07'), (4, 'Ayşe', 90, '2026-03-15');
ATTACH DATABASE 'arsiv.db' AS arsiv; -- dosya yoksa oluşturulur
SELECT seq, name FROM pragma_database_list;
-- çıktı:
-- 0|main
-- 2|arsivpragma_database_list her şema için bir satır döndürür: seq sıra numarası, name şema adı, file ise dosyanın tam yoludur. Burada yolu göstermedik, çünkü sizin dizininize göre değişir. temp şeması henüz bir geçici nesne oluşturulmadığı için listede görünmedi; 1 numaralı sıra ona ayrılmıştır. Komut satırında aynı bilgiyi .databases komutuyla da alabilirsiniz.
İki dosya arasında satır taşımak
ATTACH'in en yaygın kullanımlarından biri eski kayıtları ayrı bir arşiv dosyasına taşımaktır. Bağlı veritabanında tablo oluşturmak için tablo adının önüne şema adını yazarsınız. Belgelere göre tablolar ana veritabanında, temp şemasında ya da bağlı herhangi bir veritabanında oluşturulabilir.
CREATE TABLE arsiv.siparis(id INTEGER PRIMARY KEY, musteri TEXT, tutar REAL, tarih TEXT);
BEGIN;
INSERT INTO arsiv.siparis SELECT * FROM main.siparis WHERE tarih < '2026-01-01';
DELETE FROM main.siparis WHERE tarih < '2026-01-01';
COMMIT;
SELECT 'main', count(*) FROM main.siparis
UNION ALL
SELECT 'arsiv', count(*) FROM arsiv.siparis;
-- çıktı:
-- main|2
-- arsiv|2Kopyalama ve silme aynı işlemin içinde yapıldığı için ya ikisi birden gerçekleşir ya da hiçbiri. Ancak bu güvencenin bir koşulu var. Belgelere göre birden fazla dosyayı değiştiren işlemler ancak ana veritabanı :memory: değilse ve günlük modu WAL değilse dosyalar arasında atomiktir. Ana veritabanı bellek içindeyse ya da WAL modu kullanılıyorsa her dosya kendi içinde atomik kalır, fakat COMMIT sırasında makine çökerse dosyalardan bazıları değişikliği almış, bazıları almamış olabilir. Geri alma günlüğü modunda SQLite bu durumu ana dosyanın dizininde oluşturduğu ve işlemde değişen bütün dosyaların adını içeren bir "super-journal" dosyasıyla önler. WAL modunun ayrıntıları SQLite WAL modu, işlemler ise SQLite transaction yazısında.
Bu örnekte tabloları önceden aynı sütunlarla oluşturduk, bu yüzden SELECT * güvenle kullanılabildi. Şemalar zamanla farklılaşabileceği için üretim betiklerinde sütun adlarını açıkça yazmanızı öneririz. Tabloyu hızlıca kopyalamak için CREATE TABLE arsiv.yeni AS SELECT ... biçimi de çalışır, ancak bu yol kısıtları ve indeksleri kopyalamaz; ayrıntılar SQLite CREATE TABLE ve kısıtlamalar yazısında.
Dosyalar arası sorgu
Bağlı veritabanları sıradan tablolar gibi birleştirilebilir. Aşağıdaki sorgu canlı ve arşiv siparişlerini tek listede toplayıp müşteri bazında tutarları hesaplar. İş bittikten sonra DETACH ile dosya ayrılır.
SELECT musteri, sum(tutar)
FROM (SELECT musteri, tutar FROM main.siparis
UNION ALL
SELECT musteri, tutar FROM arsiv.siparis)
GROUP BY musteri ORDER BY musteri;
-- çıktı:
-- Ayşe|340.0
-- Mehmet|120.0
-- Zeynep|480.0
DETACH DATABASE arsiv;
SELECT seq, name FROM pragma_database_list;
-- çıktı: 0|mainBurada sıralamanın ikili karşılaştırmaya göre yapıldığını unutmayın; Türkçe harflerle başlayan adlar alfabetik beklentiden farklı sıralanabilir. Bu konu SQLite COLLATE ve NOCASE yazısında, birleşik sorgular ise SQLite SELECT, WHERE, ORDER BY yazısında anlatılıyor.
Şema adı yazılmazsa hangi tablo bulunur
Bir tablo adı bütün bağlı veritabanlarında benzersizse şema önekini yazmanıza gerek yoktur. Aynı ad birden fazla veritabanında varsa belgelerin ad çözümleme kuralı devreye girer: SQLite önce temp, sonra main, ardından bağlı veritabanlarını bağlandıkları sırayla arar ve ilk eşleşmeyi kullanır. Bir şema adı verdiğinizde ise yalnızca o şemaya bakılır. Şema adları, diğer SQL tanımlayıcıları gibi büyük-küçük harfe duyarsızdır.
ATTACH 'arsiv.db' AS arsiv; -- arsiv.db içinde de "siparis" tablosu var
SELECT count(*) FROM siparis; -- önek yok: main.siparis bulunur
-- çıktı: 2
CREATE TEMP TABLE siparis(x);
SELECT count(*) FROM siparis; -- artık temp.siparis bulunur
-- çıktı: 0
SELECT count(*) FROM main.siparis;
-- çıktı: 2
DROP TABLE temp.siparis;
SELECT count(*) FROM ARSIV.siparis; -- şema adları büyük-küçük harfe duyarsız
-- çıktı: 2Bu kural DROP TABLE gibi yıkıcı komutlarda tehlikeli olabilir. Belgelerdeki örnekte aynı adlı tablo temp, main ve bağlı bir veritabanında bulunduğunda, art arda üç kez çalıştırılan önek siz DROP TABLE t1 komutu sırasıyla temp, main ve bağlı veritabanındaki tabloyu siler. Birden fazla dosyayla çalışırken her zaman şema önekini yazmak bu tür sürprizleri önler.
Sınırlar ve hatalar

Aynı şema adını ikinci kez kullanamazsınız, ancak belgelere göre paylaşılan önbellek modu kullanılmıyorsa aynı dosya farklı adlarla birden fazla kez bağlanabilir ve birini ayırmak diğerlerini etkilemez. main ve temp ayrılamaz.
ATTACH 'arsiv.db' AS arsiv;
ATTACH 'arsiv.db' AS arsiv;
-- hata: database arsiv is already in use
ATTACH 'arsiv.db' AS arsiv2; -- aynı dosya başka adla: izin verilir
SELECT seq, name FROM pragma_database_list;
-- çıktı:
-- 0|main
-- 2|arsiv
-- 3|arsiv2
DETACH main;
-- hata: cannot detach database main
DETACH yok;
-- hata: no such database: yokBir bağlantıya aynı anda bağlanabilecek veritabanı sayısı SQLITE_MAX_ATTACHED ile sınırlıdır. Belgelere göre varsayılan değer 10'dur ve derleme sırasında en fazla 125'e çıkarılabilir; uygulama çalışırken sqlite3_limit() arayüzüyle düşürülebilir. main ve temp bu sayıya dahil değildir:
ATTACH ':memory:' AS m1; ATTACH ':memory:' AS m2; ATTACH ':memory:' AS m3;
ATTACH ':memory:' AS m4; ATTACH ':memory:' AS m5; ATTACH ':memory:' AS m6;
ATTACH ':memory:' AS m7; ATTACH ':memory:' AS m8; ATTACH ':memory:' AS m9;
ATTACH ':memory:' AS m10;
ATTACH ':memory:' AS m11;
-- hata: too many attached databases - max 10Kalıcı görünümler başka şemaya başvuramaz
Bağlı veritabanlarını sık kullanıyorsanız onları bir görünümle sarmak isteyebilirsiniz. Ana veritabanında kalıcı bir görünüm oluşturmak denememizde reddedildi; mantıklı bir kısıttır, çünkü bağlı dosya bir sonraki oturumda bağlı olmayabilir. Geçici görünüm ise çalıştı:
ATTACH 'arsiv.db' AS arsiv;
CREATE VIEW tum_siparis AS SELECT * FROM arsiv.siparis;
-- hata: view tum_siparis cannot reference objects in database arsiv
CREATE TEMP VIEW tum_siparis AS SELECT * FROM arsiv.siparis;
SELECT count(*) FROM tum_siparis;
-- çıktı: 2Tetikleyicilerde de benzer bir ayrım vardır. Belgelere göre bir tetikleyici normalde ON sonrasında adı geçen tabloyla aynı veritabanında bulunur, ancak başka bir veritabanındaki tablo için TEMP tetikleyici oluşturulabilir. Böyle bir tetikleyici yalnızca onu tanımlayan bağlantının yaptığı değişikliklerde çalışır. Tablo yeniden adlandırma da veritabanları arasında taşıma yapmaz: ALTER TABLE ... RENAME TO yalnızca aynı veritabanı içinde ad değiştirir. Görünümler için SQLite CREATE VIEW, tetikleyiciler için SQLite trigger yazısına bakın.
Geçici ve bellek içi veritabanları
Dosya adı olarak :memory: verdiğinizde bellek içi, boş metin verdiğinizde ise yeni bir geçici veritabanı bağlanır. İkisi de bağlantı kapanınca kaybolur. Ara sonuçları ana dosyayı kirletmeden tutmak için kullanışlıdır:
ATTACH '' AS gecici; -- boş ad: yeni, geçici bir veritabanı
ATTACH ':memory:' AS bellek; -- bellek içi veritabanı
CREATE TABLE gecici.ara(x);
CREATE TABLE bellek.ara(x);
SELECT seq, name, file = '' FROM pragma_database_list;
-- çıktı:
-- 0|main|0
-- 2|gecici|1
-- 3|bellek|1Bağlanan veritabanları ana veritabanıyla aynı VFS katmanını kullanır; belgeler farklı bir VFS için vfs=AD URI parametresinin kullanılabileceğini belirtir. URI dosya adları varsayılan olarak kapalıdır ve bağlantıda ayrıca etkinleştirilmeleri gerekir.
Bağlı veritabanında bakım
Pragma komutlarının çoğu şema öneki alır: PRAGMA arsiv.integrity_check, PRAGMA arsiv.page_count gibi. VACUUM de şema adıyla çalıştırılabilir ve VACUUM arsiv INTO 'dosya' biçimiyle bağlı veritabanının yedeği alınabilir. Komut satırında .backup arsiv dosya.db aynı işi yapar, .import --schema arsiv ise CSV verisini doğrudan bağlı veritabanına aktarır.
ATTACH 'arsiv.db' AS arsiv;
PRAGMA arsiv.integrity_check;
-- çıktı: ok
VACUUM arsiv;
VACUUM arsiv INTO 'arsiv-kopya.db';Bazı pragmalar şema adı verilmezse bütün bağlı veritabanlarını etkiler. Belgelere göre journal_mode şema belirtilmezse bütün bağlı veritabanlarının modunu değiştirir, secure_delete da aynı şekilde davranır ve yeni bağlanan veritabanları ana veritabanının o anki ayarını alır. Buna karşılık journal_size_limit her veritabanı için ayrı ayrı ayarlanmalıdır. Bakım komutlarının ayrıntıları SQLite VACUUM ve SQLite yedekleme yazılarında.
Uçtan uca örnek: aylık arşivleme

Yukarıdaki parçaları bir araya getirince, büyüyen bir sipariş tablosunu her ay küçük tutmak için kullanılabilecek bir bakım akışı ortaya çıkar. Adımların her biri bu yazıda tek tek denendi:
- Bakım betiğinde önce
.timeoutveyaPRAGMA busy_timeoutile kilit bekleme süresi tanımlayın. ATTACH 'arsiv-2025.db' AS arsiv;ile o yılın arşiv dosyasını bağlayın; dosya yoksa oluşur.- Arşiv tablosu yoksa
CREATE TABLE IF NOT EXISTS arsiv.siparis(...)ile ana tabloyla aynı sütunlarla oluşturun. BEGINile işlemi açın, eski satırlarıINSERT INTO arsiv.siparis SELECT ...ile kopyalayın, aynı koşullaDELETE FROM main.siparisçalıştırın veCOMMITile onaylayın.- Her iki tablodaki satır sayılarını karşılaştırın ve
PRAGMA arsiv.integrity_checkçalıştırın. DETACH arsiv;ile dosyayı ayırın. Ana veritabanında çok yer boşaldıysa uygun bir saatteVACUUMdüşünün.
Bu akışta dikkat edilecek nokta günlük modudur. Ana veritabanı WAL modundaysa, belgelerde anlatıldığı gibi kopyalama ve silme her dosyada ayrı ayrı atomiktir ama ikisi birlikte değildir; COMMIT anında bir çökme olursa satırlar her iki dosyada birden bulunabilir ya da, daha kötüsü, arşive yazılmadan ana dosyadan silinmiş görünebilir. Bu riski azaltmak için kopyalama ve silmeyi iki ayrı işleme bölüp silmeden önce arşivdeki satırları doğrulamak, ya da ana tabloda arşivlenen satırları bir işaret sütunuyla izlemek daha sağlam bir düzendir. Arşiv dosyasını ayrıca yedeklemeyi unutmayın; bir kez taşınan satırlar artık yalnızca orada durur.
Güvenlik notları
ATTACH, SQL çalıştırabilen herkesin sunucudaki başka dosyaları açmasına veya yeni dosya oluşturmasına imkân verir. Bu nedenle kullanıcıdan gelen SQL'i doğrudan çalıştıran uygulamalarda ciddi bir saldırı yüzeyidir. Güvenlik belgesindeki öneri tablosunda LIMIT_ATTACH için yüksek güvenlik değeri 0 olarak verilir, yani ATTACH tamamen kapatılır. 3.49.0 sürüm notları, ATTACH ile yeni dosya oluşturmayı ve bağlı dosyaya yazmayı denetleyen SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE ve SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE seçeneklerinin eklendiğini ve varsayılanlarının açık olduğunu belirtir. Komut satırında --safe seçeneği ATTACH komutunu devre dışı bırakır. Dosya adlarını hiçbir zaman kullanıcı girdisinden metin birleştirerek oluşturmayın; parametreli sorgular için SQL injection önleme, genel önlemler için SQLite güvenlik ayarları yazısına bakın.
Sunucu tarafında bağlanan dosyaların da ana veritabanıyla aynı kurallara uyduğunu unutmayın: uygulamayı çalıştıran kullanıcının dosyaya ve dizinine yazma izni olmalı, dosyalar yerel diskte durmalıdır. Belgeler ağ dosya sistemlerinde kilitlemenin güvenilir olmadığını vurgular.
Sık yapılan hatalar
- Şema önekini atlamak: Aynı adlı tablolar varsa önce temp, sonra main aranır; yanlış tabloyu okuyabilir veya silebilirsiniz.
- Dosya adını yanlış yazmak: Var olmayan dosya hata vermez, boş bir veritabanı olarak oluşturulur. Bağladıktan sonra
SELECT name FROM arsiv.sqlite_schemaile içeriği kontrol edin. - WAL modunda dosyalar arası atomiklik beklemek: WAL modunda veya bellek içi ana veritabanında çökme anında dosyalar farklı durumda kalabilir.
- Kalıcı görünümde bağlı tabloya başvurmak: Bu reddedilir; TEMP görünüm kullanın.
- Sınırı unutmak: Varsayılan olarak en fazla 10 veritabanı bağlanabilir.
Sık Sorulan Sorular
ATTACH bağlantı boyunca kalıcı mı?
Bağlantı açık kaldığı sürece evet; bağlantı kapanınca bağlama ortadan kalkar. Yeni bir bağlantıda ATTACH komutunu yeniden çalıştırmanız gerekir. Komut satırında .open ile başka bir dosyaya geçmek de bağlantıyı yeniler.
Farklı dosyalardaki tablolar arasında yabancı anahtar tanımlayabilir miyim?
Denememizde hayır. REFERENCES yan tümcesi şema öneki kabul etmedi ve önek siz yazılan üst tablo, alt tablonun kendi veritabanında arandı:
PRAGMA foreign_keys = ON;
ATTACH 'fk2.db' AS a;
CREATE TABLE p(id INTEGER PRIMARY KEY); -- main.p
INSERT INTO p VALUES (1);
CREATE TABLE a.c(x REFERENCES p(id)); -- a.c, üst tablo olarak "p"
INSERT INTO a.c VALUES (1);
-- hata: no such table: a.p
CREATE TABLE a.c2(x REFERENCES main.p(id));
-- hata: near ".": syntax errorDosyalar arası ilişkiyi uygulama katmanında veya TEMP tetikleyicilerle kontrol etmeniz gerekir; yabancı anahtarların ayrıntıları SQLite foreign key yazısında.
Bağlı veritabanını salt okunur açabilir miyim?
Evet, URI dosya adlarıyla. Belgelere göre ATTACH komutu da URI kabul eder ve mode=ro parametresi dosyayı salt okunur açar. URI işleme bağlantıda etkin olmalıdır; denememizdeki komut satırı aracında etkindi ve yazma denemesi reddedildi:
ATTACH 'file:arsiv.db?mode=ro' AS ro;
SELECT count(*) FROM ro.siparis;
-- çıktı: 2
INSERT INTO ro.siparis(musteri) VALUES ('deneme');
-- hata: attempt to write a readonly databaseRaporlama betiklerinde canlı veritabanını bu şekilde bağlamak, yanlışlıkla yazılan bir UPDATE veya DELETE ifadesinin veriye dokunmasını engeller. URI biçiminin diğer parametreleri için belgelerdeki URI dosya adları sayfasına bakabilirsiniz.
ATTACH yerine iki ayrı bağlantı açmalı mıyım?
Verileri aynı sorguda birleştirmeniz veya tek işlemde iki dosyayı birlikte değiştirmeniz gerekiyorsa ATTACH gerekir. Dosyalar birbirinden bağımsız kullanılıyorsa ayrı bağlantılar daha sadedir.
Bağlı veritabanının sorgu planı farklı mı?
Hayır. Bağlı veritabanındaki tablolar ve indeksler ana veritabanındakiler gibi kullanılır; plana EXPLAIN QUERY PLAN ile bakabilirsiniz.
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 yedekleme · SQLite transaction · SQLite CREATE VIEW · SQLite CSV içe ve dışa aktarma · SQLite güvenlik ayarları