← CodeClarityLab Home
Browse by Category
+ added · updated 7d
← Back to glossary

N+1 Problem in Doctrine & Eloquent

performance PHP 7.0+ Intermediate

Also Known As

ORM N+1 Eloquent N+1 lazy load N+1 ORM

TL;DR

Accidentally issuing one query per related entity instead of one JOIN — the most common ORM performance pitfall, solved by eager loading.

Explanation

The N+1 problem: fetching N orders then calling $order->customer inside a loop fires N additional queries — one per order — instead of a single JOIN. In Doctrine: use DQL JOIN FETCH or addSelect on a QueryBuilder to eager-load associations. In Eloquent: use with('customer') on the query. Detect N+1 in development with Laravel Telescope, Debugbar, or Clockwork (all show per-request query counts). In Doctrine enable SQL logging to a file. A page making 300 queries that should make 3 is the signature. Fix: identify the loop, move the JOIN upstream, verify query count drops. For deeply nested associations, consider a dedicated read-model query bypassing the ORM entirely.

Common Misconception

ORMs automatically optimise relationship loading. ORMs default to lazy loading — each relationship access on a collection item triggers a separate query. Always use eager loading (with() in Eloquent, fetch JOIN in Doctrine) when rendering collections with related data.

Why It Matters

ORMs make N+1 queries invisible — the code looks like one operation but triggers hundreds of queries, each with network round-trip overhead that adds up to seconds of latency.

Common Mistakes

  • Not enabling ORM query logging in development — N+1 queries are silent without it.
  • Lazy-loaded relationships in loops — the classic ORM N+1 pattern.
  • Using with() for eager loading but then accessing a non-eager-loaded relationship inside the loop.
  • Not using tools like Laravel Debugbar or Symfony Profiler to detect query counts per request.

Code Examples

✗ Vulnerable
$orders = $em->findAll(Order::class); // 1 query
foreach ($orders as $o) {
    echo $o->getCustomer()->getName(); // N queries!
}
✓ Fixed
$orders = $em->createQuery(
    'SELECT o, c FROM Order o JOIN FETCH o.customer c'
)->getResult(); // 1 query

Added 15 Mar 2026
Edited 22 Mar 2026
Views 29
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 1 ping S 0 pings M 0 pings T 0 pings W 0 pings T 2 pings F 1 ping S 1 ping S 1 ping M 0 pings T 0 pings W 1 ping T 2 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 1 ping T
No pings yesterday
Amazonbot 8 ChatGPT 4 Perplexity 3 Google 3 Unknown AI 2 Ahrefs 2 Majestic 1
crawler 23
DEV INTEL Tools & Severity
🟠 High ⚙ Fix effort: Low
⚡ Quick Fix
Enable Laravel's strict mode (Model::preventLazyLoading()) or Doctrine's lazy loading profiler in development — they throw exceptions when you accidentally trigger lazy loads in loops
📦 Applies To
PHP 7.0+ web cli laravel doctrine
🔗 Prerequisites
🔍 Detection Hints
Eloquent relation accessed in foreach without eager load; Doctrine proxy object loaded per iteration; Debugbar showing 50+ identical queries
Auto-detectable: ✓ Yes laravel-debugbar laravel-strict-mode doctrine-profiler clockwork
⚠ Related Problems
🤖 AI Agent
Confidence: High False Positives: Medium ✗ Manual fix Fix: Medium Context: File Tests: Update

✓ schema.org compliant