What is an N+1 query?
An N+1 query problem happens when code runs one query to load a list of N records, then one more query per record for related data, so a page with 50 orders makes 51 database round trips instead of 2. It usually comes from ORM lazy loading and is fixed with eager loading or a join.
Why an N+1 query matters
Each query is fast, so nothing looks wrong in a slow-query log. The cost is in the count: round trips add up, connections are held longer, and the page gets slower as the list grows. A page that is fine with 10 test records can be slow with 1,000 real ones.
The same pattern hides in code that calls another service in a loop, where each iteration costs a network round trip instead of a query.
How to measure an N+1 query
queries ≈ 1 + N (the list query plus one per item) time ≈ (1 + N) × round-trip time fixed: 2 queries (the list plus one IN query) or 1 join
Worked example
A page lists 50 orders and shows each order's customer. Each query takes about 1.5 ms including the network round trip.
- Lazy loading: 1 + 50 = 51 queries, about 51 × 1.5 = 76.5 ms in the database alone.
- Eager loading (
WHERE customer_id IN (…)): 2 queries, about 3 ms. - With 500 orders the lazy version needs 501 queries; the eager version still needs 2.
Result: The fix changes neither the data nor the page, only the query shape, and the cost stops growing with the list.
Common mistakes
- Testing with tiny data. With 5 rows an N+1 is invisible. Test with realistic data volumes.
- Fixing it with a cache. Caching each item hides the pattern but keeps the round trips on a cold cache. Fix the query shape.
- Over-eager loading. Loading every relation everywhere fetches data you do not use. Load what the view needs.
- Only looking at slow queries. N+1 queries are fast one by one. Look at the number of queries per request.
How Vigil handles an N+1 query
- Vigil's APM flags an N+1 when a single trace contains 8 or more queries of the same shape, and marks those rows on the trace timeline.
- Slow queries are ranked by total time (calls × duration), which surfaces fast but frequent queries that a per-query threshold misses. The Python SDK instruments SQLAlchemy and the Node SDK instruments pg and mysql2.
- The Django and Rails guides cover what else to watch in those frameworks.
N+1 query: frequently asked questions
How do I fix an N+1 query?
Load the related records in one go: select_related or prefetch_related in Django, includes in Rails, joinedload or selectinload in SQLAlchemy, with() in Laravel Eloquent. Then check the query count again.
Is N+1 always bad?
Not for tiny, bounded lists where the extra queries are cheap. It becomes a problem when N grows with your data or traffic.
Why is it called N+1?
Because the code runs 1 query for the list and then N more, one for each item in it.