Foruma hoş geldin 👋, Ziyaretçi

Forum içeriğine ve tüm hizmetlerimize erişim sağlamak için foruma kayıt olmalı ya da giriş yapmalısınız. Foruma üye olmak tamamen ücretsizdir.

Eloquent'te hasManyThrough ilişkilerinde bile Eager Loading ile veritabanı roundtrip'larını nasıl sıfıra indirdiğim

thedevx

Üye
Katılım
14 Mart 2026
Mesajlar
7
Merhaba arkadaşlar, bugün Laravel'de özellikle karmaşık ilişkilerde (hasManyThrough gibi) yaşadığımız N+1 sorgu problemini ve bunu nasıl tamamen ortadan kaldırdığımı anlatacağım. Bu hatayı ilk gördüğümde, özellikle büyük veri setlerinde, sayfanın yüklenmesinin dakikalar aldığı bir durum yaşamıştım ve gerçekten kafayı yemiştim. İşte benim kullandığım en temiz çözüm.

🔥 Karşılaştığım Sorun

Projemde bir `Country` (Ülke) modelim vardı. Bu ülkelerin birçok `City` (Şehir) kaydı vardı ve her şehrin de `Company` (Şirket) kayıtları vardı. Yani klasik bir `hasManyThrough` ilişkisi: Ülke -> Şehirler -> Şirketler.

Amacım, bir ülkenin tüm şirketlerini listelemekti. İlk başta safça, `Country` modelimde `hasManyThrough` ilişkisini tanımlayıp, `with` ile eager loading yapmaya çalıştım. Ancak, şirketlere ait ek bilgileri (örneğin şirketin bulunduğu şehir adı) göstermek istediğimde, Laravel'in ilişkileri yükleme şekli yüzünden her bir şirket için ayrı bir sorgu daha atılıyordu. Bu da korkunç bir veritabanı roundtrip'una ve performans düşüşüne neden oluyordu.

💡 Çözüm Yolum: Manuel Eager Loading ve İlişkileri Birleştirme

Eloquent'in güçlü sorgu builder'ını kullanarak, tüm veriyi tek bir (veya en kötü ihtimalle iki) sorguda çekmenin yolunu buldum. Anahtar nokta, `with` metodunun sadece ilk seviye için değil, ara modelleri de (`City` gibi) içerecek şekilde genişletilmesi ve sonrasında `hasManyThrough` mantığını manuel olarak oluşturmaktı.

İşte ilk adım, modeller arasındaki temel ilişkileri doğru tanımlamak:

PHP:
// Country Model
class Country extends Model
{
    public function cities()
    {
        return $this->hasMany(City::class);
    }

    // Klasik hasManyThrough - Sorunlu olan
    public function companies()
    {
        return $this->hasManyThrough(Company::class, City::class);
    }
}

// City Model
class City extends Model
{
    public function country()
    {
        return $this->belongsTo(Country::class);
    }

    public function companies()
    {
        return $this->hasMany(Company::class);
    }
}

// Company Model
class Company extends Model
{
    public function city()
    {
        return $this->belongsTo(City::class);
    }
}

⚙️ Sihirli Sorgu: Tüm Veriyi Tek Sorguda Çekmek

Artık asıl performans artışını sağlayacak kısma geldik. Controller veya Repository'mde, tüm ülkeleri, şehirleri ve şirketleri tek bir veritabanı sorgusunda (roundtrip) getiren sorguyu yazıyorum.

PHP:
$countries = Country::query()
    ->with([
        // Önce şehirleri yüklüyoruz
        'cities' => function ($query) {
            // Şehirleri yüklerken, o şehre ait şirketleri de EAGER load ediyoruz
            $query->with(['companies']);
        }
    ])
    ->get();

// Ya da daha da optimize, sadece ihtiyacımız olan şirketleri çekmek için:
$countriesWithCompanies = Country::query()
    ->select('countries.')
    // Şehirler tablosunu join'liyoruz
    ->join('cities', 'cities.country_id', '=', 'countries.id')
    // Şirketler tablosunu join'liyoruz
    ->join('companies', 'companies.city_id', '=', 'cities.id')
    // Gereksiz veri tekrarını önlemek için distinct uyguluyoruz
    ->distinct()
    ->with(['cities', 'cities.companies'])
    ->get();

Ancak benim favori ve en temiz yöntemim, özel bir sorgu scope'u yazmak oldu. `Country` modeline aşağıdaki scope'u ekledim:

🚀 Nihai ve En Temiz Yöntem: Özel Scope Kullanımı

PHP:
// Country Modeline eklenen scope
public function scopeWithCompaniesThroughCities($query)
{
    return $query->with(['cities' => function ($q) {
        $q->select(['id', 'name', 'country_id']) // Sadece ihtiyacımız olan sütunlar
          ->with(['companies:id,name,city_id']); // Companies'ten de sadece gerekli sütunlar
    }]);
}

// Kullanımı (Controller'da):
$optimizedCountries = Country::withCompaniesThroughCities()->get();

Bu yöntemi kullandığınızda, Laravel'in Query Builder'ı akıllıca sadece 2 sorgu atar:
1. Tüm ülkeleri ve ilişkili şehirleri getiren bir sorgu.
2. Tüm bu şehirlerin ID'leri için, ilişkili tüm şirketleri getiren TEK BİR sorgu.

Artık `foreach` döngülerinde `$country->cities->first()->companies` şeklinde erişirken sıfır ekstra sorgu atılıyor. Veritabanı roundtrip'ları neredeyse sıfıra indi!

💎 Sonuç ve Düşünceler

Bu yöntem, özellikle raporlama sistemleri veya karmaşık admin panellerinde inanılmaz bir performans artışı sağladı. Eloquent ORM güçlüdür, ancak bazen onun sorgu oluşturma mantığını yönlendirmemiz gerekir. `hasManyThrough` ilişkisi basit senaryolarda iyidir, ancak derinlemesine eager loading ve özelleştirilmiş sütun seçimi gerektiğinde manuel birleştirmeler ve özel scope'lar kurtarıcı oluyor.

Siz Laravel'de benzer karmaşık ilişki sorunları yaşadınız mı? `hasManyThrough` dışında hangi ilişki türlerinde N+1 problemi sizi çok uğraştırdı? Veya sizin kullandığınız farklı, daha da optimize bir yöntem var mı? Yorumlarda deneyimlerinizi paylaşın, tartışalım!
 

Tema özelleştirme sistemi

Bu menüden forum temasının bazı alanlarını kendinize özel olarak düzenleye bilirsiniz.

Zevkine göre renk kombinasyonunu belirle

Tam ekran yada dar ekran

Temanızın gövde büyüklüğünü sevkiniz, ihtiyacınıza göre dar yada geniş olarak kulana bilirsiniz.

Geri