Her Laravel uygulamasında yavaş sorgular bulunur. Önemli olan, bunları kullanıcılarınızdan önce tespit edebilmenizdir.
Yerli bir makinede 100 satır üzerinde 50ms süren bir sorgu, üretimde 500,000 satırla 5 saniye alabilir. Geliştirme ortamında sorunsuz çalışan bir polimorfik ilişki, morphables tablosu bir milyon kayda ulaştığında bir tam tablo taramasına dönüşebilir. Üç whereHas çağrısı içeren bir Eloquent kapsamı, yoğun trafik zamanlarında veritabanı sunucunuzu yavaşlatan iç içe bir alt sorgu canavarı oluşturur.
İyi haber: Laravel, bu sorguları kullanıcılarınızın yaşamasından önce bulmanız için mükemmel araçlar sunar. Daha iyi haber: Sorguları bulduğunuzda, düzeltmeleri genellikle basittir. Bu yazıda, Debugbar ve Telescope ile yerel tespit aşamasından, sorgu analizi için EXPLAIN’e, üretimde izleme için Deploynix’e kadar kapsamlı bir iş akışı sunulmaktadır.
Adım 1: Geliştirmede Yavaş Sorguları Tespit Etmek
Adım 1: Geliştirmede Yavaş Sorguları Tespit Etmek
İlk savunma hattı, yavaş sorguları geliştirme sırasında yakalamaktır. Bu işlemi kolaylaştıran iki araç vardır.
Laravel Debugbar
Laravel Debugbar
Debugbar, sorgu sorunlarını tespit etmenin en hızlı yoludur. Bir geliştirici bağımlılığı olarak yükleyin:
composer require barryvdh/laravel-debugbar --devHer sayfa yüklemesi, tarayıcınızın altında yürütülen sorgu sayısını ve toplam süreyi gösteren bir araç çubuğu açar. Sorgular sekmesine tıklayarak yerine getirilmiş her bireysel sorguyu, yürütme süresiyle birlikte ve tetikleyen dosya/satır ile görebilirsiniz.
Kritik metrik, toplam sorgu süresi değil — sorgu sayısıdır. Bir kontrol paneli sayfasının yüklenmesi sırasında 47 sorgu çalıştırıyorsanız, her bir bireysel sorgunun hızı ne olursa olsun N+1 sorununa sahipsiniz demektir. Bu 47 sorgu veri büyüdüğünde 4,700 sorguya çıkacaktır.
Debugbar’da aramanız gereken bazı kırmızı bayraklar:
- Sayfa başına 10-15’ten fazla sorgu
- Tekrar eden sorgular (aynı sorgu farklı ID’lerle birden fazla kez çalıştırılıyor)
- Büyük tablolarda
WHEREifadesi olmayan sorgular - Kontrolcü veya görünüm kodunuzda görünmeyen sorgular (genellikle Blade şablonlarında tembel yüklenen ilişkilerden kaynaklanır)
Laravel Telescope
Laravel Telescope
Telescope, uygulamanızın yürüttüğü her sorguyu kaydeder ve yavaş olanları otomatik olarak işaretler. Kurulumdan sonra /telescope/queries adresine giderek sürelere göre sıralanmış sorguları görüntüleyebilirsiniz.
Telescope, API uç noktaları ve kuyruklu görevler gibi Debugbar’ın ulaşamadığı yerlerde yavaş sorguları yakalamak için özellikle faydalıdır.
Telescope servis sağlayıcınızda yavaş sorgu eşiklerini yapılandırın:
Telescope::tag(function (IncomingEntry $entry) {
if ($entry->type === EntryType::QUERY && $entry->content['slow']) {
return ['slow-query'];
}
return [];
});Varsayılan yavaş sorgu eşiği 100ms’dir; bu, çoğu uygulama için makuldür. 100ms’den uzun süren herhangi bir sorgunun incelenmesi gerekmektedir.
Adım 2: EXPLAIN ile Analiz
Adım 2: EXPLAIN ile Analiz
Bir yavaş sorguyu belirledikten sonra, neden yavaş olduğunu anlamanın zamanı geldi. MySQL’in EXPLAIN komutu, veritabanının sorguyu nasıl yürüteceğini tam olarak söyler.
Debugbar veya Telescope’dan aldığı ham SQL ile birlikte EXPLAIN komutunu çalıştırın:
EXPLAIN SELECT * FROM orders
WHERE user_id = 42
AND status = 'pending'
AND created_at > '2026-01-01'
ORDER BY created_at DESC;Çıktı, sorgu yürütme planınızı gösterir. Şunlara dikkat edin:
type sütunu: Bu en önemli göstergedir.
ALLtüm tablo taraması anlamına gelir. Büyük bir tabloda bu genellikle sorun demektir.indextüm indeks taraması anlamına gelir. BuALL‘dan daha iyi, ama yine de indeks içindeki her satırı okur.rangesorgunun bir indeks kullanarak sürüm seçtiği aralıkları belirtir. Bu genellikle kabul edilebilir.refsorgunun eşleşen satırları bulmak için bir indeks kullandığını belirtir. Bu iyi bir durumdur.eq_refeşsiz bir indeks araması anlamına gelir. Bu optimaldir.constsorgunun en fazla bir satır eşleştiğini gösterir. Bu en hızlı olası aramadır.
rows sütunu: MySQL’in inceleyeceği tahmini satır sayısını gösterir. Bu sayı, tablodaki toplam satır sayısına yakınsa, yine de tam bir tarama yapıyorsunuz demektir.
Extra sütunu: “Using filesort” (MySQL sonuçları bir indeks olmadan sıralamaktadır) ve “Using temporary” (MySQL geçici bir tablo oluşturur) gibi durumlar için dikkatli olun. Bu durumlar, büyük veri setlerinde potansiyel performans problemlerini gösterebilir.
Laravel’dan EXPLAIN Çalıştırmak
Laravel’dan EXPLAIN Çalıştırmak
Eloquent’in toSql() metodunu kullanarak ham SQL’i alabilir, ardından veritabanı istemcisinde EXPLAIN çalıştırabilirsiniz. Ya da sorgu oluşturucunun explain() metodunu doğrudan kullanın:
$results = Order::query()
->where('user_id', 42)
->where('status', 'pending')
->where('created_at', '>', '2026-01-01')
->orderByDesc('created_at')
->explain()
->get();Bu, EXPLAIN çıktısını inceleyebileceğiniz veya kaydedebileceğiniz bir koleksiyon olarak döner.
Adım 3: Yaygın Yavaş Sorgu Desenleri ve Çözümleri
Adım 3: Yaygın Yavaş Sorgu Desenleri ve Çözümleri
Desen 1: WHERE Sütunlarında Kayıp İndeks
Desen 1:
WHERE Sütunlarında Kayıp İndeksProblem:
Order::where('status', 'pending')->get();status üzerinde bir indeks yoksa, MySQL siparişler tablosundaki her satırı tarar.
Çözüm:
// Bir migration oluşturun
Schema::table(, function (Blueprint $table) {
$table->index();
});
Desen 2: Birleşik WHERE ile Birleşik İndeks Olmadan
Desen 2: Birleşik
WHERE ile Birleşik İndeks OlmadanProblem:
Order::where(, $userId)
->where(, )
->orderByDesc()
->get();Bireysel indeksler user_id, status ve created_at olsa bile, MySQL basit bir sorguda tabloda yalnızca bir indeks kullanabilir. En seçici olanı seçer ve geri kalanları tarar.
Çözüm:
Tüm üç sütunu doğru sırayla kapsayan bir birleşik indeks oluşturun:
Schema::table(, function (Blueprint $table) {
$table->index([, , ]);
});Sütun sırası önemlidir. Eşitlik koşullarını (user_id, status) aralık koşullarından (created_at) önce koyun ve sütunları sıralayın.
Desen 3: Karmaşık Koşullarla whereHas Kullanma
Desen 3: Karmaşık Koşullarla
whereHas KullanmaProblem:
Product::whereHas(, function ($query) {
$query->where( , 4);
})->get();whereHas, MySQL tarafından dış tablo üzerinde her satır için bir kez yürütülen bir ilişkilendirilmiş alt sorgu oluşturur. Büyük tablolarda bu yıkıcıdır.
Çözüm:
Bunun yerine bir JOIN kullanın:
Product::select()
->join(, , , )
->where( , 4)
->distinct()
->get();Ayrıca, ürünler tablosuna reviews_avg_rating sütunu ekleyerek verileri denormalize edebilir ve bunu bir gözlemci veya olay dinleyicisi aracılığıyla güncelleyebilirsiniz.
Desen 4: İndeks Olmadan Sıralama
Desen 4: İndeks Olmadan Sıralama
Problem:
Post::where(, true)
->orderByDesc()
->paginate(20);Hem WHERE hem de ORDER BY sütunlarını kapsayan bir indeks yoksa, MySQL tüm eşleşen satırları alır ve sonra bunları bellek üzerinde sıralar. EXPLAIN’deki “Using filesort” bayrağı bunu doğrular.
Çözüm:
Schema::table(, function (Blueprint $table) {
$table->index([, ]);
});
Desen 5: Gerekenden Fazla Sütun Seçme
Desen 5: Gerekenden Fazla Sütun Seçme
Problem:
$users = User::all();
// Ardından bir Blade görünümünde:
@foreach($users as $user)
{{ $user->name }}
@endforeachTüm sütunları yüklemiş oluyorsunuz (bu da muhtemel büyük metin alanlarını, JSON sütunlarını ve ikili verileri içeriyor) oysa yalnızca isme ihtiyacınız var.
Çözüm:
$users = User::select([, ])->get();Bu, bellek kullanımını ve veritabanı ile uygulama sunucusu arasındaki ağ transferini azaltır. Büyük text veya json sütunlarına sahip tablolar üzerinde bu fark dramatik olabilir.
Desen 6: Sınırsız Sorgular
Desen 6: Sınırsız Sorgular
Problem:
$logs = ActivityLog::where(, $userId)->get();Bir kullanıcının 50,000 etkinlik günlüğü kaydı olduğunda, tüm 50,000 kaydı bir kerede belleğe yüklersiniz.
Çözüm:
Paginasyon veya chunking (parçalara ayırma) kullanın:
// Görüntüleme için
$logs = ActivityLog::where(, $userId)
->latest()
->paginate(25);
// İşleme için
ActivityLog::where(, $userId)
->chunkById(500, function ($logs) {
// Her seferinde 500'ü işle
});
Adım 4: Otomatik Tespit ile Regresyonları Önlemek
Adım 4: Otomatik Tespit ile Regresyonları Önlemek
Laravel 12, tembel yüklemeyi uygulama genelinde önlemenizi sağlayarak N+1 sorunlarını geliştirme sırasında istisnalara dönüştürür:
// bootstrap/app.php veya bir servis sağlayıcıda
Model::preventLazyLoading(! app()->isProduction());Üretimde, sayfaları çökertmeyen istisnaları istemezsiniz. Bunun yerine, tembel yükleme ihlallerini kaydedin:
Model::preventLazyLoading();
Model::handleLazyLoadingViolationUsing(function ($model, $relation) {
$class = get_class($model);
logger()->warning("Lazy loading [{$relation}] on model [{$class}].");
});Bu, kullanıcıları rahatsız etmeden düzeltmeniz gereken N+1 problemlerinin bir kayıt izini sağlar.
Adım 5: Üretimde Sorguları Deploynix ile İzlemek
Adım 5: Üretimde Sorguları Deploynix ile İzlemek
Geliştirme testi yalnızca bu kadar gider. Yerel veritabanınız verilerin bir kısmına sahiptir, sunucu farklı kaynaklara sahiptir ve trafik desenleri kolayca simüle edilemeyen sorunlar yaratır.
Deploynix’in sunucu izleme özelliği, MySQL sürecinin CPU ve bellek kullanımını gerçek zamanlı olarak izler. Yavaş bir sorgu kaynak tüketmeye başladığında, bu durumu veritabanı sunucusunun metriklerinde kullanıcılarla yüzleşmeden önce göreceksiniz.
Veritabanı sunucunuzda 80%’nin üzerinde MySQL CPU kullanımı devam ederken size bildirim gönderen sağlık uyarıları ayarlayın. Bu, sorgunun sunucunun çökmesine neden olmadan incelemeniz için size bir pencere verir.
Deploynix’in altyapı izleme özelliğini Laravel Pulse’ın üretimdeki yavaş sorgu takibi ile birleştirin. Pulse, hangi sorguların yavaş olduğunu size söyler. Deploynix, o yavaş sorguların gerçekten sunucu kaynaklarını etkileyip etkilemediğini gösterir. 500ms süren bir sorgu, günde iki kez çalışıyorsa düşük öncelikli bir düzeltmedir. Ama 200ms süren ve saatte 10,000 kez çalışan bir sorgu acil bir durumdur — ve Deploynix’in CPU grafikleri bunu açıkça gösterecektir.
Adım 6: Sorgu Performans Bütçesi Kurmak
Adım 6: Sorgu Performans Bütçesi Kurmak
Ekibiniz için somut eşikler belirleyin:
- Sayfa yükleme sorguları: Sayfa başına maksimum 15 sorgu
- API uç noktası sorguları: İstek başına maksimum 10 sorgu
- Bireysel sorgu süresi: Geliştirmede 100ms’yi aşmayan tek bir sorgu
- İstek başına toplam sorgu süresi: %95’in altında 50ms
Bunları otomatik testlerle uygulayın:
it(, function () {
$user = User::factory()->create();
$queryCount = 0;
DB::listen(function () use ($queryCount) {
$queryCount++;
});
$this->actingAs($user)
->get()
->assertOk();
expect($queryCount)->toBeLessThan(15);
});Bu test, birisi kontrol paneline N+1 sorunu eklediğinde başarısız olur, bu da kod incelemesi öncesinde bu sorunun tespit edilmesini sağlar.
Sonuç
Sonuç
Yavaş sorgular, uygulamanız büyüdükçe kaçınılmazdır. Ancak bunların üretime ulaşmasına izin vermek kaçınılmaz değildir.
Debugbar ve Telescope’u kullanarak geliştirme sırasında sorunları yakalayın. Şüpheli sorguları EXPLAIN ile analiz ederek neden yavaş olduklarını anlayın. Yaygın düzeltmeleri uygulayın — uygun indeksleme, eager loading, sorgu yeniden yapılandırması. Çökmeleri önlemek için preventLazyLoading ve sorgu sayısı testlerini kullanın. Ve Deploynix ile üretimi izleyerek yalnızca ölçeklendirme sırasında sorun haline gelen sorguları yakalayın.
Güvenle dağıtan geliştiriciler, mükemmel sorgular yazanlar değil, mükemmel olmayan sorguları kullanıcılar hissetmeden önce bulup düzeltmek için sistemler oluşturanlardır.
Kaynak: Orijinal Makale
- Adım 1: Geliştirmede Yavaş Sorguları Tespit Etmek
- Adım 2: EXPLAIN ile Analiz
- Adım 3: Yaygın Yavaş Sorgu Desenleri ve Çözümleri
- Desen 1: WHERE Sütunlarında Kayıp İndeks
- Desen 2: Birleşik WHERE ile Birleşik İndeks Olmadan
- Desen 3: Karmaşık Koşullarla whereHas Kullanma
- Desen 4: İndeks Olmadan Sıralama
- Desen 5: Gerekenden Fazla Sütun Seçme
- Desen 6: Sınırsız Sorgular
- Adım 4: Otomatik Tespit ile Regresyonları Önlemek
- Adım 5: Üretimde Sorguları Deploynix ile İzlemek
- Adım 6: Sorgu Performans Bütçesi Kurmak
- Sonuç


