← Back to Blog
LaravelPHPMySQLPerformance

N+1 Queries in Laravel: Detection and Elimination at Scale

The Problem No One Talks About Until It's Too Late

Your local environment runs fine. Tests pass. Then you hit 10,000 users and your database server catches fire. N+1 queries are often the culprit.

Consider a basic relationship:

$posts = Post::all();
foreach ($posts as $post) {
    echo $post->author->name; // SELECT * FROM users WHERE id = ?
}

For 100 posts, this executes 101 queries. For 10,000 posts: 10,001 queries.

Detection with Laravel Telescope

Enable query watching in Telescope and filter by request path. Any endpoint showing a query count that scales linearly with record count is a candidate for investigation.

// In a service provider (dev only)
DB::listen(function ($query) {
    if ($query->time > 100) { // milliseconds
        Log::warning('Slow query', [
            'sql' => $query->sql,
            'time' => $query->time,
        ]);
    }
});

The Fix: Eager Loading

// Bad — triggers a query per iteration
$posts = Post::all();

// Good — single JOIN resolves the relationship
$posts = Post::with(['author', 'tags', 'comments.author'])->get();

For conditional relationships, use when:

$posts = Post::with(['author'])
    ->when($request->include_comments, fn($q) => $q->with('comments'))
    ->paginate(20);

Select Only What You Need

Post::with(['author:id,name,avatar'])
    ->select(['id', 'title', 'user_id', 'created_at'])
    ->paginate(20);

Selecting specific columns dramatically reduces memory usage and network transfer between your app and database. At scale, this matters more than eager loading alone.

Lazy Eager Loading for Conditional Data

Sometimes you need to load relationships after the initial query. Use loadMissing instead of load to avoid redundant queries when the relationship may already be loaded:

$posts->loadMissing(['comments' => function ($query) {
    $query->where('approved', true)->with('author:id,name');
}]);

Monitoring in Production

The Debugbar package adds a query count badge in dev. For production, track query counts per request in your APM (Datadog, New Relic) and alert when any endpoint exceeds a threshold. Catching a 50-query endpoint before it reaches 50,000 users is always cheaper than the firefight after.