Django ORM'in temel kullanımını bildiğinizi, yani filter, exclude, annotate ve select_related gibi ifadeleri rahatça yazabildiğinizi varsayıyorum. Bu rehber, sorguların veritabanına nasıl gittiğini ve üretim ortamında nasıl daha az maliyetle çalıştırılabileceğini ele alır. Amaç mikrobir optimizasyon değil; N+1 problemlerini, gereksiz sorguları ve yanlış kullanılan yardımcı metotları erken yakalamak.
Sürüm notu: Buradaki API'ler Django 4.x ve 5.x serilerinde ortak olarak bulunur. QuerySet.explain() çıktısının biçimi veritabanı arka ucuna göre değişir; bu yazıda belirli bir sürüm numarası veya çıktı örneği verilmemektedir.
Önce Sorguları Görün: QuerySet.explain()
Optimizasyonun ilk adımı tahmin değil gözlemdir. Django, çalıştırılan sorgunun veritabanı planını görmek için explain() sunar:
qs = Order.objects.filter(status="paid")
print(qs.explain())
explain() metodu, sorguyu çalıştırmadan plan bilgisini döndürür. Dikkat edilmesi gereken nokta şudur: explain() yalnızca sorgunun kendisini değerlendirir; model üzerinde tanımlı default manager filtreleri, ilgili nesnelerin sonradan yüklenmesi gibi katmanları göstermez. Dolayısıyla planı okumak, "kaç sorgu çalışıyor" sorusunun cevabını tek başına vermez. Onun için ikinci adım gerekir.
Kaç Sorgu Çalışıyor? N+1'in Kaynağı
En sık karşılaşılan performans sorunu, bir döngü içinde ilişkili nesnelere erişmektir:
for order in Order.objects.all():
print(order.customer.name) # her satır için ayrı sorgu
Burada Order nesneleri tek sorguda gelir, ancak her order.customer erişimi ayrı bir sorgu tetikler. Çözüm, ilişki türüne göre doğru yardımcı metodu seçmektir.
select_related: ForeignKey ve OneToOne ilişkilerinde SQLJOINüretir. Tek sorguda ilişkili satırı getirir.prefetch_related: ManyToMany ve ters ForeignKey ilişkilerinde ayrı bir sorgu çalıştırır, sonucu Python tarafında eşleştirir.
orders = Order.objects.select_related("customer").all()
Bu kullanımda order.customer erişimi ek sorgu üretmez. Buna karşılık select_related'i ManyToMany alanına uygulamaya çalışmak hata verir; çoklu satır dönebilecek ilişkiler prefetch_related ile alınmalıdır.
orders = Order.objects.prefetch_related("items").all()
prefetch_related iki sorgu çalıştırır: biri ana nesneler, diğeri ilişkili nesneler için. İlişkili sorguya filtre uygulamak istiyorsanız Prefetch nesnesini kullanın:
from django.db.models import Prefetch
orders = Order.objects.prefetch_related(
Prefetch("items", queryset=OrderItem.objects.filter(quantity__gt=1))
)
Prefetch olmadan prefetch_related("items__product") gibi zincirleme yapmak mümkündür, ancak ara tabloya koşul eklemek istediğinizde Prefetch gerekir. Yanlış kullanımın tipik sonucu şudur: filtresiz prefetch tüm ilişkili satırları belleğe çeker ve veritabanı tarafında değil uygulama tarafında filtrelemeye çalışırsınız.
only ve defer: Sütun Seçimi
Geniş tablolarda tüm sütunları çekmek gereksiz I/O demektir. only yalnızca belirtilen alanları, defer ise belirtilenler dışındaki alanları yükler:
Order.objects.only("id", "status")
Order.objects.defer("notes")
Kritik ayrıntı: only ile dışarıda bıraktığınız bir alana erişirseniz Django ek bir sorgu çalıştırır. Yani only("id") çekip ardından order.status okumak, beklenmedik bir gecikmeye yol açar. Bu yüzden only/defer kullanırken nesnenin hangi alanlarına erişildiğini bilmek gerekir.
Toplu İşlemler ve Bellek
Büyük tabloları tek seferde list() ile belleğe almak yerine iterator() kullanın:
for order in Order.objects.iterator(chunk_size=2000):
process(order)
iterator() sonuçları akış halinde tüketir ve varsayılan olarak sorgu sonucu önbelleğini kullanmaz. Bu, bellek kullanımını düşürür; ancak aynı QuerySet üzerinde tekrar tekrar döngü kurmayı beklemeyin, çünkü önbellek devre dışıdır.
Toplu güncelleme için update() ve toplu oluşturma için bulk_create() tercih edilir:
Order.objects.filter(status="pending").update(status="processing")
OrderItem.objects.bulk_create(items, batch_size=1000)
update() tek SQL ifadesi çalıştırır ve save() gibi pre_save/post_save sinyallerini tetiklemez. Aynı şekilde bulk_create() de save() çağrılarını atlar ve varsayılan olarak pk değerlerini nesnelere geri yazmaz. Bu davranış, sinyal tabanlı iş mantığınız varsa sessizce atlanan adımlar yaratabilir.
annotate ve aggregate: Doğru Aracı Seçin
annotate her satır için ek alan üretir; aggregate tüm sorgu için tek bir sözlük döndürür. Karıştırılması yaygın bir hatadır:
from django.db.models import Count, Sum
# Her müşteri için sipariş sayısı
Customer.objects.annotate(order_count=Count("orders"))
# Tüm siparişlerin toplam tutarı
Order.objects.aggregate(total=Sum("amount"))
annotate ile Count kullanırken dikkat edilmesi gereken bir nokta, ilişkili birden fazla alanı aynı sorguda saymaktır. İki farklı Count aynı QuerySet üzerinde birleştirildiğinde satır çoğalması nedeniyle yanlış sonuçlar çıkabilir. Bu durumda distinct=True kullanmak veya sayımları ayrı QuerySet'lere bölmek gerekir.
Güvenlik ve Yan Etkiler
extra()metodu eski ve riskli kabul edilir; kullanıcı girdisiyle birleştirildiğinde SQL enjeksiyonuna açıktır. Yerineannotate,F,QveFuncifadeleri tercih edilmelidir.raw()veyacursor.execute()ile yazılan sorgularda parametreleri asla string birleştirmeyle vermeyin; sürücünün parametre mekanizmasını kullanın.only/deferile gizlenen alanlar, hassas veriyi yanlışlıkla log veya serileştirme katmanına taşıyabilir. Hangi alanların dışarıda bırakıldığını bilin.select_relatedbüyükJOIN'ler üretir; çok geniş ilişkilerde tek sorgu daha yavaş olabilir. Planı ölçmeden karar vermeyin.
Yaygın Hatalar
select_related'i ManyToMany veya ters ForeignKey için kullanmaya çalışmak (hata verir;prefetch_relatedgerekir).prefetch_related'i filtresiz bırakıp tüm ilişkili satırları çekmek.onlyile dışarıda bırakılan alana erişip farkında olmadan ek sorgu tetiklemek.update()vebulk_create()'in sinyal vesave()davranışlarını atladığını unutmak.annotateile çokluCount'ları düşünmeden birleştirmek.explain()çıktısını "kaç sorgu çalışıyor" sorusunun cevabı sanmak.
Pratik Kontrol Listesi
- Şüphelendiğiniz
QuerySetiçinexplain()çıktısını inceleyin. - Döngü içinde ilişkili nesneye erişiliyorsa
select_relatedveyaprefetch_relatedekleyin. - İlişkili sorguya koşul gerekiyorsa
Prefetchkullanın. - Geniş tablolarda
only/deferile sütun seçin, ancak erişilen alanları doğrulayın. - Büyük sonuç kümelerinde
iterator()vebatch_sizetercih edin. - Toplu yazma işlemlerinde sinyal ve
pkdavranışını gözden geçirin. extra()ve elle yazılmış SQL'den kaçının; parametreli sorgular kullanın.
Bu adımlar, çoğu üretim sorununun kaynağı olan gereksiz sorgu ve bellek yükünü azaltır. Ancak her optimizasyonun kazancı veri hacmine ve indeks yapısına bağlıdır; ölçmeden uygulamayın.