DJANGO ORM QUERYSET NEDİR?

Django geliştiricilerinin büyük bölümü QuerySet'i "veritabanından satır getiren bir liste" sanır. Bu yanılgı yıllarca fark edilmeden taşınabilir — taa ki bir sayfa açılışı 200 sorguya çıkana kadar. Gerçekte QuerySet bir liste değildir; tembel (lazy) bir sorgu nesnesidir, gerçekten ihtiyaç duyulana kadar veritabanına dokunmaz. Bu makale Django ORM'in en kritik soyutlamasını sıfırdan ele alıyor: bir QuerySet ne zaman çalışır, neden zincirlenir, performans tuzakları nerede gizlenir. Web tarafında çalışan ya da öğrenmeye yeni başlayan Python geliştiricileri için Django'nun veri katmanını net biçimde kavramak temel bir kazanım.
1. QuerySet Tam Olarak Nedir
QuerySet, bir model üzerinde tanımladığınız veritabanı sorgusunu temsil eden Python nesnesidir. Article.objects.filter(published=True) yazdığınızda Python bellekte bir QuerySet instance oluşturur; bu noktada SQL henüz çalışmamıştır. Veritabanına gidiş, ancak siz sonucu iterate ettiğinizde, bir slice aldığınızda, listeye çevirdiğinizde veya len() çağırdığınızda gerçekleşir.
Django'nun tasarım kararı şudur: sorgu inşası ile sorgu çalıştırması ayrı aşamalardır. Bu sayede aynı QuerySet üzerine birden çok filtre üst üste eklenebilir, oluşan tek SQL sorgusu veritabanına en sonunda gider.
2. Lazy Evaluation Davranışı
Tembellik QuerySet'in karakter özelliğidir. Aşağıdaki kod hangi satırda veritabanına gider, hangi satırda gitmez?
q = Article.objects.filter(published=True)
q = q.filter(category="python")
q = q.order_by("-created_at")
q = q[:10]
for article in q:
print(article.title)İlk dört satır sıfır SQL üretir. Yalnızca son satırdaki for döngüsü veritabanını tetikler. Django o anda dört yapılandırmayı tek SQL içine derler:
SELECT * FROM articles
WHERE published = true AND category = 'python'
ORDER BY created_at DESC
LIMIT 10;Bu davranışı bilmek view ile servis katmanı arasında QuerySet'leri taşımayı, koşullara göre dinamik sorgu inşa etmeyi mümkün kılar. Aynı zamanda bir tuzak da içerir: aynı QuerySet'i bir template içinde iki kez iterate ederseniz, Django sonucu cache'lediğinden ikinci tur bellekten okur. Ama iki ayrı QuerySet'i ardışık iterate ediyorsanız iki sorgu gider.
3. Sorgu Zincirleme ve Filtre Mantığı
QuerySet metotlarının çoğu yeni bir QuerySet döndürür. Yani filter(), exclude(), order_by(), annotate() orijinali bozmaz; üzerine yeni katman ekler. Bu immutability sayesinde zincirleme güvenlidir:
filter(field=value)— koşulu karşılayan satırları tutarexclude(field=value)— koşulu karşılayanları çıkarırorder_by("-field")— sıralar; başındaki tire azalan demekdistinct()— yinelenen satırları tekervalues("field")— model instance yerine dict döndürürannotate()— her satıra hesaplanmış alan ekler
Birden fazla filter() zincirlemek AND mantığı kurar. OR için Q objesi kullanılır: filter(Q(published=True) | Q(featured=True)). Field lookup'ları çift alt çizgiyle yazılır: title__icontains="django", created_at__gte=date(2026, 1, 1), author__name__startswith="A". Son örnekte ikinci alt çizgi ilişki üzerinden ilerler — Django ORM JOIN'i sizin için kurar. Lookup operatörlerinin tam listesi ve örnek kullanımları için resmi sorgu rehberi başvurulabilecek en güncel kaynaktır.

4. QuerySet'i Gerçekten Çalıştıran Tetikleyiciler
Veritabanına ne zaman gidildiğini bilmek, performans analizinde temeldir. QuerySet aşağıdaki durumlarda kesinlikle SQL üretir:
forile iterationlist(qs)dönüşümü- Slicing — step içeriyorsa:
qs[::2] bool(qs),if qs:len(qs)— alternatif:qs.count()daha verimlidir- Pickling, repr, template'te ilk dolaşım
exists(),count(),first(),last(),get(),aggregate()çağrıları
Sıkça karıştırılan iki nokta: qs[5:10] (offset+limit) sorgu çalıştırmaz, ancak qs[5:10:2] step içerdiği için Django sonucu çekip Python tarafında step'ler. Ayrıca if qs: tüm satırları çekip değerlendirir — sadece varlık testi yapacaksanız if qs.exists(): kullanın, o yalnızca SELECT 1 LIMIT 1 üretir.
5. N+1 Sorgu Problemi ve select_related
Django ORM ile yazılmış kodun en yaygın performans sorunu N+1'dir. İlişkili bir alan template'te ya da loop içinde her satır için ayrı sorgu üretirse, listenizdeki 100 makale 1 + 100 = 101 sorguya patlar.
# Sorunlu kod — N+1
articles = Article.objects.all()
for article in articles:
print(article.author.name) # her satır için ek SELECTÇözüm yazarı baştan JOIN ile çekmektir:
# Tek sorgu — JOIN ile
articles = Article.objects.select_related("author")
for article in articles:
print(article.author.name)select_related ForeignKey ve OneToOne ilişkilerinde SQL JOIN üretir. Tek seferde ilişkili tablonun kolonlarını da çeker. ManyToMany veya reverse ForeignKey için JOIN tekrarlı satır üreteceğinden farklı bir araç gerekir: prefetch_related. Bu yöntem iki ayrı sorgu çalıştırır ve eşleştirmeyi Python tarafında yapar.
# M2M ve reverse ForeignKey için
articles = Article.objects.prefetch_related("tags", "comments")Karar şu basit kuralla yapılır: ilişki "tek tarafa" doğruysa select_related, "çok tarafa" doğruysa prefetch_related. Django backend tarafına ciddi şekilde girmek için Django eğitimi ORM optimizasyonu, view katmanı ve REST framework entegrasyonunu birlikte ele alır.
6. QuerySet Üzerinde Yapılan Yaygın Hatalar
Üretimde sık görülen kalıplar:
- count() yerine len() kullanmak:
len(qs)tüm satırları belleğe çeker;qs.count()SQLCOUNT(*)üretir, satırları getirmez - exists() yerine if qs: Varlık testi için tüm satırları çekmek;
qs.exists()tek satır bile getirmez - QuerySet'i listeye çevirip filtrelemek:
list(qs)sonrasında Pythonforile süzmek; filtreyi ORM seviyesinde tutmak çok daha hızlıdır - only() ve defer() yanlış kullanımı: Bu metotlar belirli kolonları erteler/seçer; gerekmediği yerde eklenirse her erişimde ek sorgu doğurabilir
- order_by'sız Paginator: Sıralama olmadan sayfalanan sorgu kayan sonuçlar üretir; aynı sayfa iki kez farklı sıra dönebilir
7. QuerySet Caching ve Yeniden Değerlendirme
Bir QuerySet ilk kez iterate edildiğinde sonucu kendi içinde cache'ler. Aynı instance ikinci kez döndürüldüğünde veritabanına gitmez. Ancak QuerySet üzerinde yeni bir metot çağırırsanız (örneğin qs.filter(...)) bu yeni bir QuerySet üretir, cache aktarılmaz.
Pratikte bunun anlamı: aynı sonucu birden fazla yerde kullanacaksanız QuerySet'i list() ile veya bir değişkene atayıp iterate ederek bir kez çalıştırın. Şablon (template) tarafında aynı QuerySet'i iki ayrı bloğa geçiriyorsanız view içinde önceden bir kez dolaşmanız ya da değerlendirmeniz tutarlı performans verir. Python web geliştirme tarafını derinleştirmek için Python eğitimi faydalı bir başlangıçtır.
8. Ne Zaman QuerySet, Ne Zaman Raw SQL
ORM hızlı geliştirme ve okunabilirlik için tasarlanmıştır. Raporlama sorguları, çok adımlı CTE'ler veya veritabanı-spesifik özelliklere ihtiyaç duyan analitik sorgular için ORM zorlanır. Django bu durumda iki kapı sunar: Manager.raw() bir SQL stringini model instance'larına döndürür; connection.cursor() tamamen kontrolü size bırakır.
Genel kural: CRUD ve liste/detay tipi okumalarda QuerySet, raporlama ve agresif optimizasyon gereken yerlerde raw SQL. İkisinin arasında Django Func, Subquery, Window gibi ileri ORM yapıları da bulunur — gerçek dünyada bunlar raw SQL'e olan ihtiyacın büyük bölümünü kapatır.
Sıkça Sorulan Sorular
QuerySet ile Manager arasındaki fark nedir?
Manager bir modelin veritabanı arayüzüdür ve QuerySet üretmeye yarar. Article.objects ifadesindeki objects bir Manager'dır. QuerySet ise üzerinde filtre, sıralama ve toplama yapılabilen sorgu nesnesidir. Manager genelde QuerySet metotlarını proxy eder ama özelleştirilmiş Manager'lar default queryset davranışını değiştirebilir.
Aynı QuerySet'i iki kez iterate ettiğimde ikinci tur veritabanına gidiyor mu?
Hayır. QuerySet ilk iterasyonda sonucu cache'ler ve aynı instance üzerindeki sonraki turlar bellekten okur. Ama queryset üzerinde yeni bir metot çağırırsanız (filter, exclude vs.) bu yeni bir QuerySet üretir ve cache aktarılmaz, ilk iterasyon yeniden sorgu çalıştırır.
select_related ve prefetch_related arasında nasıl seçim yaparım?
ForeignKey ve OneToOne gibi tek-yönlü ilişkilerde select_related SQL JOIN üretir ve tek sorguda her şeyi getirir. ManyToMany ve reverse ForeignKey gibi çok-yönlü ilişkilerde prefetch_related ayrı sorgu çalıştırır ve eşleştirmeyi Python tarafında yapar. Tek satır karşılığı tek sonuç varsa select_related, birden çok ilişkili satır varsa prefetch_related.
qs.count() ile len(qs) arasındaki fark nedir?
qs.count() SQL COUNT(*) çalıştırır ve sadece sayıyı döndürür, satırları çekmez. len(qs) ise tüm satırları belleğe yükleyip Python listesinin uzunluğunu döndürür. Büyük tablolarda len() ciddi bellek ve süre maliyeti yaratır. Eğer satırları zaten kullanacaksanız tek seferde liste alıp ondan len almak mantıklıdır.
QuerySet üzerinde transaction nasıl yönetilir?
Django'nun transaction.atomic context manager'ı kod bloğunu atomik bir transaction içine alır. with transaction.atomic(): bloğu içindeki tüm ORM çağrıları başarılı olursa commit edilir, herhangi bir exception fırlatılırsa tamamı rollback olur. Karmaşık iş kuralları için savepoint'ler ile iç içe atomic kullanımı da desteklenir.
QuerySet'ten dict mi yoksa model instance mı dönmeli?
Sadece birkaç alana ihtiyacınız varsa ve model methodlarını çağırmayacaksanız values() veya values_list() ile dict/tuple dönmek belleği rahatlatır. Model instance her satır için Python objesi inşa eder, alan sayısı çoksa yavaşlar. API serializasyonu, agresif raporlama ve büyük listeler için values varyantları tercih edilir.
filter ile get arasındaki fark nedir?
filter() koşulu sağlayan tüm satırları içeren bir QuerySet döndürür, hiç sonuç yoksa boş QuerySet verir. get() ise tek satır döndürür ve sıfır sonuçta DoesNotExist, birden fazla sonuçta MultipleObjectsReturned exception fırlatır. Tekil arama (örn. id ile) için get; liste/filtreleme için filter kullanılır.


