Python'da SQLite veritabanı kullanmak için standart sqlite3 modülü yeterlidir; ayrı bir sunucu ya da paket gerekmez. sqlite3.connect("dosya.db") bir bağlantı açar (dosya yoksa oluşturur), execute() SQL çalıştırır, ? yer tutucuları değerleri güvenle bağlar, commit() değişiklikleri kalıcı yapar ve fetchone(), fetchall() ya da doğrudan döngüyle sonuçları okursunuz. Bağlantıyı iş bitince close() ile kapatmanız gerekir. Python 3.12'den beri önerilen işlem (transaction) yönetimi connect(..., autocommit=False) ile yapılır.
Bu yazıda bağlantı ve imleç (cursor) kavramlarını, SQL enjeksiyonunu önleyen parametreli sorguları, işlem kontrolünü, bağlantının with bloğundaki gerçek davranışını, sqlite3.Row ile sütun adıyla okumayı, tip eşlemesini ve Python 3.13, 3.14 ve 3.15'teki değişiklikleri göreceksiniz. SQLite'ın kendisi, yani ne olduğu ve hangi durumlarda tercih edildiği SQLite nedir yazısında anlatılıyor; burada yalnızca Python modülüne odaklanıyoruz. Örnekler Python 3.15.2 ile çalıştırıldı ve sırayla aynı magaza.db dosyasını kullanır. Serinin tamamı için Python öğrenme rehberi sayfasına bakın.
Bağlantı, imleç ve ilk tablo

connect() bir Connection nesnesi döndürür. Dosya yolu yerine ":memory:" verirseniz veritabanı yalnızca bellekte yaşar; testler ve denemeler için idealdir. SQL çalıştırmak için bir imleç (Cursor) oluşturursunuz. Belgelerin öğretici bölümü bir ayrıntıyı vurgular: SQLite'ın esnek tip sistemi sayesinde sütun tiplerini yazmak isteğe bağlıdır. Yine de INTEGER PRIMARY KEY, NOT NULL ve UNIQUE gibi kısıtlar yazmak veri bütünlüğü için faydalıdır.
import sqlite3
con = sqlite3.connect("magaza.db", autocommit=False)
cur = con.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS urun(
id INTEGER PRIMARY KEY,
ad TEXT NOT NULL UNIQUE,
fiyat REAL NOT NULL,
stok INTEGER DEFAULT 0
)
""")
cur.execute("INSERT INTO urun(ad, fiyat, stok) VALUES(?, ?, ?)", ("Çay bardağı", 49.9, 120))
print(cur.lastrowid, cur.rowcount)
# çıktı: 1 1
veriler = [("Kupa", 89.0, 40), ("Demlik", 349.5, 12), ("Tepsi", 150.0, 0)]
cur.executemany("INSERT INTO urun(ad, fiyat, stok) VALUES(?, ?, ?)", veriler)
print(cur.rowcount)
# çıktı: 3
con.commit()
for satir in cur.execute("SELECT id, ad, fiyat FROM urun ORDER BY fiyat DESC"):
print(satir)
# çıktı: (3, 'Demlik', 349.5)
# çıktı: (4, 'Tepsi', 150.0)
# çıktı: (2, 'Kupa', 89.0)
# çıktı: (1, 'Çay bardağı', 49.9)
print(cur.execute("SELECT COUNT(*) FROM urun").fetchone())
# çıktı: (4,)
print(cur.execute("SELECT ad FROM urun WHERE stok = 0").fetchall())
# çıktı: [('Tepsi',)]
con.close()lastrowid, execute() ile yapılan son başarılı INSERT işleminin satır kimliğini verir. Belgelere göre executemany() bu değeri güncellemez; toplu ekleme yaptıysanız ve kimliklere ihtiyacınız varsa satırları tek tek eklemeniz ya da sonradan sorgulamanız gerekir. rowcount ise değişen satır sayısını verir. Sonuçlar varsayılan olarak tuple'dır; tek sütunlu sorguda bile (4,) gibi tek elemanlı tuple gelir, değeri almak için [0] ile indekslemeniz ya da (sayi,) = ... biçiminde açmanız gerekir.
Connection nesnesinin de execute(), executemany() ve executescript() kısayol metotları vardır. Bunlar arka planda bir imleç oluşturup döndürür, bu yüzden basit betiklerde ayrı imleç oluşturmanız gerekmez. Sonraki örneklerde bu kısayolları kullanacağız. close() ise kısayol değildir ve otomatik çağrılmaz.
| Metot | Ne döndürür | Not |
|---|---|---|
| fetchone() | Sonraki satır ya da None | Tek kayıt beklenen sorgular |
| fetchall() | Kalan tüm satırların listesi | Küçük sonuçlar; büyük tabloda belleği doldurabilir |
| fetchmany(n) | En fazla n satırlık liste | Parça parça işleme |
| for satir in cur | Satırları birer birer | Büyük sonuçlar için bellek dostu |
| lastrowid | Son INSERT ile eklenen satırın kimliği | executemany() sonrasında güncellenmez |
| rowcount | Etkilenen satır sayısı | INSERT, UPDATE, DELETE sonrasında anlamlı |
Parametreli sorgular ve SQL enjeksiyonu
Belgelerin en sert uyarısı budur: sorguları Python'un metin işlemleriyle (f-string, %, +) birleştirmeyin. Kullanıcıdan gelen bir değer tırnağı kapatıp kendi SQL'ini ekleyebilir. Bunun yerine DB-API'nin parametre bağlama özelliğini kullanın: sorguda yer tutucu yazın, değerleri ikinci argüman olarak verin. İki yer tutucu biçimi desteklenir: soru işareti (?, qmark) ve adlandırılmış (:ad, named). Soru işaretli biçimde değerleri yer tutucu sayısıyla aynı uzunlukta bir dizi (genellikle tuple), adlandırılmış biçimde bir sözlük olarak verirsiniz.
import sqlite3
con = sqlite3.connect("magaza.db", autocommit=False)
aranan = "Kupa' OR '1'='1"
guvensiz = f"SELECT ad FROM urun WHERE ad = '{aranan}'"
print(guvensiz)
# çıktı: SELECT ad FROM urun WHERE ad = 'Kupa' OR '1'='1'
print(con.execute(guvensiz).fetchall())
# çıktı: [('Demlik',), ('Kupa',), ('Tepsi',), ('Çay bardağı',)]
print(con.execute("SELECT ad FROM urun WHERE ad = ?", (aranan,)).fetchall())
# çıktı: []
print(con.execute("SELECT ad, stok FROM urun WHERE fiyat < :ust AND stok > :alt",
{"ust": 200, "alt": 0}).fetchall())
# çıktı: [('Çay bardağı', 120), ('Kupa', 40)]
try:
con.execute("SELECT ad FROM urun WHERE ad = ?", "Kupa")
except sqlite3.ProgrammingError as e:
print(type(e).__name__ + ":", e)
# çıktı: ProgrammingError: Incorrect number of bindings supplied. The current statement uses 1, and there are 4 supplied.
con.close()İlk sorgu, tek bir ürünü aramak isterken tablonun tamamını döndürdü; enjeksiyonun en basit hâli budur. Parametreli sorgu ise aynı metni yalnızca bir değer olarak ele aldı ve böyle adlı bir ürün olmadığı için boş liste döndü. Son örnek çok sık yapılan bir hatayı gösteriyor: tek bir değeri tuple içine koymayı unutmak. "Kupa" metni de bir dizi olduğu için modül onu dört ayrı karakter olarak gördü ve “1 yer tutucu var, 4 değer verildi” hatası verdi. Doğrusu ("Kupa",) yazmaktır; sondaki virgül tek elemanlı tuple için gereklidir. Tuple konusunun ayrıntıları tuple ve unpacking yazısında anlatılıyor.
Python 3.14 ile bir kural sıkılaştırıldı: adlandırılmış yer tutucular içeren bir sorguya sözlük yerine bir dizi vermek artık ProgrammingError fırlatıyor; bu kullanım 3.12'den beri kullanımdan kaldırılmış durumdaydı. Ayrıca belgeler, PEP 249'daki sayısal yer tutucuların (:1 gibi) desteklenmediğini, kullanılırsa adlandırılmış yer tutucu gibi yorumlandığını belirtir.
İşlem kontrolü: commit, rollback ve autocommit
Veritabanında yaptığınız değişiklikler bir işlem (transaction) içinde gerçekleşir ve commit() çağrılana kadar kalıcı olmaz. Belgeler Python 3.12'den beri işlem davranışını autocommit özniteliğiyle kontrol etmeyi öneriyor ve bunun tercihen connect() çağrısında verilmesini istiyor. autocommit=False PEP 249'a uygun davranışı seçer: modül her zaman açık bir işlem bulundurur, commit() ve rollback() mevcut işlemi kapatıp hemen yenisini açar, bağlantı bekleyen değişikliklerle kapatılırsa değişiklikler geri alınır. autocommit=True ise SQLite'ın kendi otomatik kayıt kipini açar; bu kipte commit() ve rollback() hiçbir şey yapmaz.
autocommit parametresinin varsayılan değeri ise geriye uyumluluk için LEGACY_TRANSACTION_CONTROL'dür. Bu durumda eski isolation_level kuralları geçerlidir: INSERT, UPDATE, DELETE ve REPLACE öncesinde örtük olarak bir işlem açılır, diğer ifadelerde açılmaz. Eski kodlarda gördüğünüz “commit yapmayı unuttum, veriler kayboldu” sorunlarının çoğu bu kipten gelir. Yeni kodda autocommit=False yazmak davranışı açık ve öngörülebilir kılar. Bir uyarı daha: executescript() hangi kipte olursanız olun, betiği çalıştırmadan önce bekleyen işlemi örtük olarak kaydeder.
| autocommit değeri | İşlem davranışı | commit() ve rollback() |
|---|---|---|
| False (önerilen) | Her zaman açık bir işlem vardır (BEGIN DEFERRED) | İşlemi kapatır, yenisini açar |
| True | SQLite'ın otomatik kayıt kipi | Etkisizdir |
| LEGACY_TRANSACTION_CONTROL (varsayılan) | isolation_level kuralları; DML öncesi örtük işlem | Bekleyen işlemi kapatır |
with con: kaydeder ama kapatmaz
Connection nesnesi bir bağlam yöneticisi (context manager) olarak kullanılabilir, ama davranışı çoğu kişinin beklediği gibi değildir. with con: bloğu hatasız biterse işlem kaydedilir (commit), blok içinde yakalanmamış bir istisna olursa geri alınır (rollback) ve istisna yine yukarı fırlatılır. Belgelerin özellikle not ettiği gibi bağlam yöneticisi bağlantıyı kapatmaz. Kapatma da istiyorsanız contextlib.closing() kullanmanız önerilir. Bu ayrım, bir bloğun “işlem” mi yoksa “kaynak” mı yönettiğini anlamak açısından önemlidir; bağlam yöneticilerinin genel mantığı with ve context manager yazısında anlatılıyor.
import sqlite3
con = sqlite3.connect("magaza.db", autocommit=False)
try:
with con:
con.execute("UPDATE urun SET stok = stok - 5 WHERE ad = ?", ("Kupa",))
con.execute("INSERT INTO urun(ad, fiyat) VALUES(?, ?)", ("Kupa", 10))
except sqlite3.IntegrityError as e:
print(type(e).__name__ + ":", e)
# çıktı: IntegrityError: UNIQUE constraint failed: urun.ad
print(con.execute("SELECT stok FROM urun WHERE ad = 'Kupa'").fetchone())
# çıktı: (40,)
with con:
con.execute("UPDATE urun SET stok = stok - 5 WHERE ad = ?", ("Kupa",))
print(con.execute("SELECT stok FROM urun WHERE ad = 'Kupa'").fetchone())
# çıktı: (35,)
print(con.in_transaction, con.autocommit)
# çıktı: True False
con.close()İlk blokta stok düşürüldü ama hemen ardından aynı adla ikinci bir ürün eklenmeye çalışıldı ve UNIQUE kısıtı ihlal edildi. Blok istisnayla bittiği için stok düşürme işlemi de geri alındı; stok 40 olarak kaldı. Bu “ya hep ya hiç” davranışı, örneğin bir siparişte stoktan düşme ve sipariş kaydını birlikte yapmak için idealdir. İkinci blok başarıyla bittiği için değişiklik kaydedildi. Son satırda in_transaction değerinin True olması da beklenen davranıştır: autocommit=False kipinde modül kayıttan hemen sonra yeni bir işlem açar.
Python 3.13'ten beri açıkça kapatılmayan bir bağlantı çöp toplayıcı tarafından temizlenirken ResourceWarning: unclosed database in <sqlite3.Connection object at ...> uyarısı üretilir. Bu uyarı varsayılan olarak gösterilmez, ama -W default ile ya da test çalıştırıcılarında görünür hâle gelir. Bağlantıyı açıp kapatmayı unutmamanın en kolay yolu closing() kullanmaktır.
import sqlite3
from contextlib import closing
with closing(sqlite3.connect(":memory:", autocommit=False)) as con:
con.execute("CREATE TABLE t(x)")
con.execute("INSERT INTO t VALUES(?)", (None,))
con.execute("INSERT INTO t VALUES(?)", (b"\x00\x01",))
con.execute("INSERT INTO t VALUES(?)", (3.5,))
for (deger,) in con.execute("SELECT x FROM t"):
print(repr(deger))
print(con.execute("SELECT typeof(x) FROM t").fetchall())
# çıktı: None
# çıktı: b'\x00\x01'
# çıktı: 3.5
# çıktı: [('null',), ('blob',), ('real',)]Python ve SQLite tip eşlemesi

SQLite yerel olarak beş tip tanır: NULL, INTEGER, REAL, TEXT ve BLOB. Python tarafında bunların karşılığı sırasıyla None, int, float, str ve bytes'tır. Yukarıdaki örnekte tip belirtilmemiş tek bir sütuna üç farklı tip yazıldı ve her biri kendi tipiyle geri okundu; SQLite'ın esnek tip sistemi budur. Bu beş tipin dışındaki değerler, örneğin datetime ya da Decimal, için adaptör (Python'dan SQLite'a) ve dönüştürücü (SQLite'tan Python'a) kaydedebilirsiniz.
| Python | SQLite | SQLite'tan okununca |
|---|---|---|
| None | NULL | None |
| int | INTEGER | int |
| float | REAL | float |
| str | TEXT | str (text_factory ile değiştirilebilir) |
| bytes | BLOB | bytes |
sqlite3.Row ile sütun adıyla okuma
Tuple'lar sütun sırasına bağımlıdır; sorguya bir sütun eklediğinizde indeksler kayar. row_factory özniteliği her satırın hangi nesneye dönüştürüleceğini belirler. Belgeler bunu imleç yerine bağlantı üzerinde ayarlamayı önerir, böylece o bağlantıdan oluşturulan tüm imleçler aynı biçimi kullanır. Hazır sqlite3.Row sınıfı hem indeksle hem de büyük/küçük harfe duyarsız sütun adıyla erişim sağlar ve belgelere göre tuple'a kıyasla çok az bellek ve performans maliyeti getirir. dict(satir) ile sözlüğe de çevrilebilir; bu, sonucu JSON'a yazmak için kullanışlıdır.
import sqlite3
con = sqlite3.connect("magaza.db", autocommit=False)
con.row_factory = sqlite3.Row
satir = con.execute("SELECT id, ad, fiyat FROM urun WHERE ad = ?", ("Demlik",)).fetchone()
print(type(satir).__name__, satir.keys())
# çıktı: Row ['id', 'ad', 'fiyat']
print(satir[1], satir["fiyat"], satir["AD"], dict(satir))
# çıktı: Demlik 349.5 Demlik {'id': 3, 'ad': 'Demlik', 'fiyat': 349.5}
con.close()Daha fazla esneklik gerekiyorsa row_factory olarak kendi fonksiyonunuzu verebilirsiniz. Fonksiyon (cursor, row) argümanlarını alır; cursor.description içindeki sütun adlarını kullanarak bir sözlük, namedtuple ya da veri sınıfı döndürebilir. Belgelerde sözlük ve namedtuple döndüren iki hazır tarif bulunur.
Türkçe metin, sıralama ve büyük/küçük harf
SQLite metinleri UTF-8 olarak saklar ve Python str değerleri sorunsuz gider gelir. Sıralama ve karşılaştırma ise başka konudur. Aşağıdaki deneme, varsayılan sıralamanın karakter kodlarına göre yapıldığını ve SQLite'ın NOCASE karşılaştırmasının Türkçe harfleri kapsamadığını gösteriyor: “ali” ve “Ali” eşleşti ama aynı sorgu “ışık” için yalnızca küçük harfli kaydı buldu, “Işık” dışarıda kaldı. Python tarafında create_function() ile kendi fonksiyonunuzu SQL'e tanıtabilirsiniz, ancak Python'un lower() metodu da dile duyarlı değildir: “Işık” küçültüldüğünde “ışık” değil “işık” olur ve eşleşme yine kaçar.
import sqlite3
con = sqlite3.connect(":memory:", autocommit=False)
con.execute("CREATE TABLE kisi(ad TEXT)")
con.executemany("INSERT INTO kisi VALUES(?)", [("ışık",), ("Işık",), ("ali",), ("Ali",)])
print(con.execute("SELECT ad FROM kisi ORDER BY ad").fetchall())
# çıktı: [('Ali',), ('Işık',), ('ali',), ('ışık',)]
print(con.execute("SELECT ad FROM kisi WHERE ad = 'ali' COLLATE NOCASE").fetchall())
# çıktı: [('ali',), ('Ali',)]
print(con.execute("SELECT ad FROM kisi WHERE ad = 'ışık' COLLATE NOCASE").fetchall())
# çıktı: [('ışık',)]
con.create_function("kucuk", 1, lambda s: s.lower() if s else s)
print(con.execute("SELECT ad FROM kisi WHERE kucuk(ad) = ?", ("ışık",)).fetchall())
# çıktı: [('ışık',)]
con.close()Türkçe I/ı ve İ/i dönüşümlerini doğru yapmak için kendi eşleme tablonuzu kullanmanız gerekir; bu konu Unicode ve Türkçe karakter yazısında ayrıntılı olarak ele alınıyor. Bir seçenek de, kayıt sırasında kendi kurallarınızla normalleştirilmiş ayrı bir arama sütunu doldurup sorguları o sütun üzerinden yapmaktır.
Python 3.15 ve öncesindeki değişiklikler

Python 3.15'te sqlite3.Connection API'si sadeleştirildi. connect() fonksiyonunun database dışındaki tüm parametreleri artık yalnızca anahtar kelimeyle verilebiliyor; create_function() ve create_aggregate() metotlarının ilk üç parametresi ise yalnızca konumsal oldu. Eski kodlarda görülen sqlite3.connect("x.db", 5.0) gibi bir çağrı 3.15.2'de TypeError: connect() takes at most 1 positional arguments (2 given) hatası verir; doğrusu timeout=5.0 yazmaktır. Komut satırı arayüzü (python -m sqlite3) de 3.15'te Tab ile SQL anahtar sözcüğü ve tablo, sütun gibi şema adı tamamlama ile renkli istemler kazandı.
- 3.15:
connect()parametreleri (database hariç) yalnızca anahtar kelimeyle; komut satırında Tab tamamlama ve renkli çıktı. - 3.14:
versionveversion_infokaldırıldı, SQLite kütüphane sürümü içinsqlite_versionvesqlite_version_infokullanılır; adlandırılmış yer tutucuya dizi vermekProgrammingErrorfırlatır. - 3.13: kapatılmayan bağlantı için
ResourceWarning;iterdump()metodunafilterparametresi. - 3.12:
autocommitözniteliği ve önerilen işlem kontrolü; varsayılan tarih adaptörlerinin kullanımdan kaldırılması;python -m sqlite3komut satırı arayüzü.
python -m sqlite3 magaza.db
python -m sqlite3 magaza.db "SELECT ad, stok FROM urun"
python -m sqlite3 -vKomut satırı kabuğundan .quit yazarak ya da CTRL-D ile çıkılır. Sürümün diğer yenilikleri için Python 3.15 yenilikleri yazısına bakabilirsiniz.
Sık yapılan hatalar
- Sorguyu f-string ile kurmak. Her zaman
?ya da:adyer tutucuları kullanın. - Tek değeri tuple'a koymamak:
(deger)değil(deger,)yazın. commit()çağırmayı unutmak ve bağlantı kapanınca değişikliklerin geri alınması.with con:bloğunun bağlantıyı kapattığını sanmak. Yalnızca işlemi kaydeder ya da geri alır.executemany()sonrasındalastrowiddeğerine güvenmek.- Büyük sonuçlarda
fetchall()ile tüm tabloyu belleğe almak. İmleç üzerinde döngü kurun. - 3.15'e geçerken
connect()çağrılarında konumsal parametre kullanmaya devam etmek.
Sık Sorulan Sorular
sqlite3 için ayrıca kurulum gerekir mi?
Genellikle hayır; modül Python ile birlikte gelir. Belgeler modülün isteğe bağlı (optional) bir modül olduğunu ve üçüncü taraf SQLite kütüphanesine dayandığını belirtir; Python kopyanızda bulunmuyorsa Python'u size sağlayan dağıtıcının belgelerine bakmanız önerilir. Bu yazının örnekleri resmî Windows embeddable paketinde hiçbir ek kurulum yapılmadan çalıştı.
Aynı veritabanını birden fazla iş parçacığından kullanabilir miyim?
connect() fonksiyonunun check_same_thread parametresi varsayılan olarak True'dur ve bağlantının yalnızca onu oluşturan iş parçacığında kullanılmasını denetler. En basit yol her iş parçacığına kendi bağlantısını açmaktır. Başka bir bağlantı tabloyu kilitlediğinde timeout (varsayılan beş saniye) kadar beklenir, ardından OperationalError fırlatılır.
Sorgu sonucunu sözlük olarak nasıl alırım?
con.row_factory = sqlite3.Row ayarlayıp her satırı dict(satir) ile çevirebilir ya da belgelerdeki dict_factory tarifini kullanabilirsiniz.
CSV dosyasını SQLite tablosuna nasıl aktarırım?
Dosyayı csv.reader ile okuyup satırları executemany("INSERT INTO ... VALUES(?, ?, ?)", okuyucu) ile tek seferde ekleyebilirsiniz; okuyucu bir yineleyici olduğu için bellek sorunu olmaz. CSV tarafı için csv modülü yazısına bakın.
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: Python öğrenme rehberi · SQLite nedir · with ve context manager · csv modülü · datetime ile tarih ve saat · json modülü · try-except hata yönetimi · Django deploy rehberi