Amblem
Furkan Baytekin

N+1 Query Problem: Fixing It with SQL and Prisma ORM

Solve the N+1 query problem and optimize your database performance

N+1 Query Problem: Fixing It with SQL and Prisma ORM
56
6 minutes

The N+1 query problem is a common performance pitfall in database-driven applications, causing unnecessary database queries that slow down your application. In this post, we’ll dive into the N+1 query problem, illustrate it with SQL and Prisma ORM examples, and show how to optimize queries for better performance.

What is the N+1 Query Problem?

The N+1 query problem occurs when an application executes one initial query (the “1”) and then N additional queries for each item returned by the first query. This inefficiency leads to excessive database calls, increasing latency and degrading performance.

Consider a database with two tables: Users and Posts, where each user has multiple posts. Fetching all users and their posts inefficiently can trigger the N+1 problem.

Database Schema Overview

Here’s the schema for our example:

Table Columns
Users id (PK), name, email
Posts id (PK), user_id (FK), title, content

This represents a one-to-many relationship: one user can have multiple posts.

N+1 Query Problem in SQL

Let’s explore how the N+1 problem appears in raw SQL and how to fix it.

Faulty SQL: Triggering the N+1 Problem

To fetch all users and their posts, you might write:

sql
-- 1. Fetch all users SELECT * FROM Users; -- 2. Fetch posts for each user (repeated N times) SELECT * FROM Posts WHERE user_id = ?;

What happens:

The first query retrieves all users. For each user, a separate query fetches their posts.

For 100 users, this results in 101 queries: one for users and 100 for posts. This scales poorly, especially with large datasets.

Correct SQL: Optimizing with JOIN

Use a JOIN to fetch users and posts in a single query:

sql
-- Fetch users and their posts in one query SELECT u.id, u.name, u.email, p.id AS post_id, p.title, p.content FROM Users u LEFT JOIN Posts p ON u.id = p.user_id;

Key optimizations:

LEFT JOIN includes users without posts (use INNER JOIN if you only want users with posts). Selecting specific columns (e.g., u.id, p.title) instead of * reduces data transfer. This approach uses one query, drastically improving performance.

Performance Tip: For 100 users, the N+1 approach might take ~2 seconds (20ms per query), while the JOIN could complete in ~50ms. Use EXPLAIN in your database to analyze query performance.

N+1 Query Problem in Prisma ORM

Prisma ORM simplifies database interactions in Node.js, but it’s still susceptible to the N+1 problem. Let’s see how it happens and how to fix it.

Faulty Prisma Code: N+1 in Action

Here’s a common mistake when fetching users and posts:

ts
// 1. Fetch all users const users = await prisma.user.findMany(); // 2. Fetch posts for each user (N+1 problem) const postsPromises = users.map(user => prisma.post.findMany({ where: { userId: user.id } }) ); // Wait for all queries to resolve const posts = await Promise.all(postsPromises);

This code:

For 100 users, this results in 101 queries, mirroring the SQL issue.

Correct Prisma Code: Eager Loading with include

Prisma’s eager loading with include solves this:

ts
// Fetch users and posts in one query const usersWithPosts = await prisma.user.findMany({ include: { posts: true // Fetch related posts } });

For better performance, select only needed fields:

ts
// Optimized: Fetch specific fields const usersWithPosts = await prisma.user.findMany({ select: { id: true, name: true, email: true, posts: { select: { id: true, title: true } } } });

This approach:

Debugging Tip: Enable Prisma’s query logging to detect N+1 issues:

ts
const prisma = new PrismaClient({ log: ['query'] });

Advanced Scenario: Multiple Relationships

What if you need users, posts, and comments? The N+1 problem can compound.

Faulty SQL Approach

sql
-- Fetch users SELECT * FROM Users; -- Fetch posts for each user SELECT * FROM Posts WHERE user_id = ?; -- Fetch comments for each post SELECT * FROM Comments WHERE post_id = ?;

This creates a nested N+1 problem, with queries for posts and comments per user.

Correct SQL Approach

Use nested JOINs:

sql
SELECT u.id, u.name, p.id AS post_id, p.title, c.id AS comment_id, c.content FROM Users u LEFT JOIN Posts p ON u.id = p.user_id LEFT JOIN Comments c ON p.id = c.post_id;

This fetches all data in one query.

Faulty Prisma Approach

ts
const users = await prisma.user.findMany(); const usersWithPostsAndComments = await Promise.all( users.map(async user => { const posts = await prisma.post.findMany({ where: { userId: user.id } }); const postsWithComments = await Promise.all( posts.map(post => prisma.comment.findMany({ where: { postId: post.id } }) ) ); return { ...user, posts: postsWithComments }; }) );

This results in excessive queries.

Correct Prisma Approach

Use nested include:

ts
const usersWithPostsAndComments = await prisma.user.findMany({ include: { posts: { include: { comments: true } } } });

This fetches all data in one query.

Why the N+1 Query Problem Matters

The N+1 problem impacts:

Optimizing queries with JOIN or eager loading ensures scalability and performance.

Tools to Detect N+1

SQL: Use EXPLAIN or profiling tools (e.g., pgAdmin, MySQL Workbench). Prisma: Enable query logging or use prisma-erd-generator for schema visualization. Monitoring: Tools like New Relic or Datadog highlight repetitive queries.

Conclusion

The N+1 query problem is a critical issue in database-driven applications, but it’s solvable with proper techniques. By using JOIN in SQL and eager loading in Prisma, you can minimize queries and boost performance. Whether you’re writing raw SQL or using Prisma, understanding query optimization is key to building scalable applications.

Have you tackled the N+1 problem in your projects? Share your tips in the comments!

Key Takeaways


Album of the day:

Suggested Blog Posts