Database query performance issue: N+1 problem
Introduction
I’ve heard of the “N+1 problem” since I first encountered databases, but I never took notes or thought it through thoroughly. This time I document the causes, detailed solutions, and diagrams.
What is the N+1 problem?
The “N+1 problem” mainly refers to performing one initial query (1) and then executing N additional related queries for each of the N items returned by the first query. This pattern easily causes serious performance issues.
- 1: One query to find all records matching certain criteria
- N: For each record from 1, perform a related query
Here’s the translated Mermaid diagram:
How to solve the N+1 problem?
The issue is the repeated and excessive access to the database; if you can fetch the required data in one go, you can solve the problem.
- Data modeling (denormalization)
- Perform related-data queries at the DB layer:
$lookuporJOIN
db.users.aggregate([ { $lookup: { from: "posts", localField: "_id", foreignField: "userId", as: "posts" } }])- Batch queries and assemble in the application layer:
$inorIN
// Step 1: Query all postsconst posts = await Post.find(); // 100 posts
// Step 2: Collect all author IDsconst userIds = [...new Set(posts.map(p => p.userId))];
// Step 3: Batch query all authors (execute only once)const users = await User.find({ _id: { $in: userIds } });
// Step 4: Assemble at the application layerconst userMap = new Map(users.map(u => [u._id, u]));posts.forEach(post => { post.author = userMap.get(post.userId);});
userMap.get(userId)- ORM/ODM Eager Loading
Summary
- Avoid making database queries inside loops.
- Understand how your ORM issues queries: Many ORMs default to lazy loading and can generate inefficient queries that lead to N+1 problems. They usually provide configuration or features to avoid this—check the documentation to ensure your ORM generates efficient queries.