Giris
Django ORM, SQL yazmadan veritabani islemleri yapmanizi saglar ancak yanlis kullanildiginda her istekte yuzlerce gereksiz sorgu uretebilir. QuerySet'ler lazy'dir: degerlendirilene kadar SQL calismaz. Bu gecikme guclu bir ozelliktir ama dongu icinde iliskili nesnelere erisim N+1 problemini dogurur.
Bu rehberde QuerySet optimizasyon tekniklerini, Django Debug Toolbar ve connection.queries ile profillemeyi ve buyuk tablolarda guvenli sayfalama stratejilerini ele aliyoruz.
QuerySet Temelleri ve Lazy Evaluation
Post.objects.filter(status='published') henuz SQL uretmez. list(), len(), iterate veya slice islemleri sorguyu tetikler. queryset.count() icin ayri bir COUNT sorgusu calisir; bu yuzden len(queryset) yerine count() tercih edilmelidir.
- all(), filter(), exclude(), order_by() yeni QuerySet doner
- values() ve values_list() dict/tuple sonuc
- exists() ve count() optimize edilmis sorgular
qs = Post.objects.filter(status='published') # SQL yok
for post in qs:
print(post.title) # Tek SELECT
# Kotu: her post icin ayri sorgu
for post in Post.objects.all():
print(post.author.username) # N+1!N+1 Problemi ve Tespiti
N+1: 1 sorgu ana kayitlari getirir, N sorgu her satir icin iliskili veriyi ceker. 100 blog yazisi listesinde yazar adi gosterirken 101 sorgu olusabilir. django-debug-toolbar veya logging ile connection.queries uzunlugunu izleyin.
# Kotu
posts = Post.objects.all()
for p in posts:
print(p.author.email) # Her iterasyonda SELECT
# Iyi
posts = Post.objects.select_related('author')
for p in posts:
print(p.author.email) # Tek JOINTemplate icinde {{ post.author.name }} kullaniyorsaniz view'da select_related planlayin.
select_related: ForeignKey ve OneToOne
select_related SQL JOIN kullanir; ForeignKey ve OneToOne iliskilerde tek sorguda veriyi getirir. Zincirleme iliskiler icin cift alt cizgi kullanilir: select_related('author__profile').
Post.objects.select_related(
'author',
'category',
).filter(status='published')
# CBV'de
class PostDetailView(DetailView):
queryset = Post.objects.select_related('author', 'category')prefetch_related: ManyToMany ve Ters FK
prefetch_related ayri sorgularla veriyi bellege alir ve Python tarafinda eslestirir. ManyToMany ve reverse ForeignKey icin uygundur. Prefetch() nesnesi ile alt QuerySet filtrelenebilir.
- select_related: JOIN, tek sorgu
- prefetch_related: ayri sorgular + bellek eslestirme
- Her ikisi birlikte kullanilabilir
from django.db.models import Prefetch
Post.objects.prefetch_related(
'tags',
Prefetch(
'comments',
queryset=Comment.objects.filter(is_approved=True),
),
)only, defer ve annotate
only() ve defer() belirli alanlari yukler veya haric tutar; buyuk TEXT/BLOB kolonlarda bant genisligi tasarrufu saglar. annotate() ve aggregate() SQL seviyesinde hesaplama yapar; Python dongusunden kacinir.
from django.db.models import Count, Avg
Post.objects.annotate(
comment_count=Count('comments'),
avg_rating=Avg('comments__rating'),
).filter(comment_count__gte=5)
Post.objects.only('id', 'title', 'slug').defer('body')Profilleme ve Uretim Ipuclari
Gelistirmede django-debug-toolbar SQL panelini kullanin. Uretimde Sentry veya APM ile yavas endpoint'leri izleyin. iterator(chunk_size=2000) buyuk export islerinde bellek tuketimini dusurur.
- View'da gereken iliskileri onceden yukle
- Pagination ile LIMIT/OFFSET kullan
- Indeksleri migration'da tanimla
- Raw SQL yalnizca olculmus darbogazlarda
for row in Post.objects.iterator(chunk_size=500):
export_row(row)Sonuc
ORM kolayligi performans maliyeti getirmez; dogru QuerySet zinciri ile hem okunabilir hem hizli kod yazilir. Her optimizasyonu olcmeden uygulamayin; bazen ekstra JOIN yerine cache daha uygun olabilir.
- select_related/prefetch_related'i template ihtiyacina gore secin
- exists() bos kontrol icin count()'tan hizli
- bulk_create ve bulk_update toplu islemlerde kullanin