apps/docs/content/docs/orm/prisma-client/queries/advanced/query-optimization-performance.mdx
This guide covers identifying and optimizing query performance.
Common causes of slow queries:
Prisma Optimize provides recommendations to address these issues. Follow the integration guide to get started.
It is generally more performant to read and write large amounts of data in bulk - for example, inserting 50,000 records in batches of 1000 rather than as 50,000 separate inserts. PrismaClient supports the following bulk queries:
PrismaClient or use connection pooling to avoid database connection pool exhaustionCreating multiple instances of PrismaClient can exhaust your database connection pool, especially in serverless or edge environments, potentially slowing down other queries. Learn more in the serverless challenge.
For applications with a traditional server, instantiate PrismaClient once and reuse it throughout your app instead of creating multiple instances. For example, instead of:
async function getPosts() {
const prisma = new PrismaClient();
await prisma.post.findMany();
}
async function getUsers() {
const prisma = new PrismaClient();
await prisma.user.findMany();
}
Define a single PrismaClient instance in a dedicated file and re-export it for reuse:
export const prisma = new PrismaClient();
Then import the shared instance:
import { prisma } from "db.ts";
async function getPosts() {
await prisma.post.findMany();
}
async function getUsers() {
await prisma.user.findMany();
}
For serverless development environments with frameworks that use HMR (Hot Module Replacement), ensure you properly handle a single instance of Prisma in development.
The n+1 problem occurs when looping through query results and performing one additional query per result.
findUnique() with the fluent APIPrisma's dataloader automatically batches findUnique() queries in the same tick. Use the fluent API to return related data:
// Instead of findMany per user, use:
return context.prisma.user
.findUnique({ where: { id: parent.id } })
.posts();
relationLoadStrategyconst posts = await prisma.post.findMany({
relationLoadStrategy: "join",
where: { authorId: parent.id },
});
where filter are on scalar fields (unique or non-unique) of the same model you're querying.equal filter, whether that's via the shorthand or explicit syntax (where: { field: <val>, field1: { equals: <val> } }).Automatic batching of findUnique() is particularly useful in a GraphQL context. GraphQL runs a separate resolver function for every field, which can make it difficult to optimize a nested query.
For example - the following GraphQL runs the allUsers resolver to get all users, and the posts resolver once per user to get each user's posts (n+1):
query {
allUsers {
id,
posts {
id
}
}
}
The allUsers query uses user.findMany(..) to return all users:
const Query = objectType({
name: "Query",
definition(t) {
t.nonNull.list.nonNull.field("allUsers", {
type: "User",
resolve: (_parent, _args, context) => {
return context.prisma.user.findMany();
},
});
},
});
This results in a single SQL query:
{
timestamp: 2021-02-19T09:43:06.332Z,
query: 'SELECT `dev`.`User`.`id`, `dev`.`User`.`email`, `dev`.`User`.`name` FROM `dev`.`User` WHERE 1=1 LIMIT ? OFFSET ?',
params: '[-1,0]',
duration: 0,
target: 'quaint::connector::metrics'
}
However, the resolver function for posts is then invoked once per user. This results in a findMany() query ✘ per user rather than a single findMany() to return all posts by all users (expand CLI output to see queries).
const User = objectType({
name: "User",
definition(t) {
t.nonNull.int("id");
t.string("name");
t.nonNull.string("email");
t.nonNull.list.nonNull.field("posts", {
type: "Post",
resolve: (parent, _, context) => {
return context.prisma.post.findMany({
where: { authorId: parent.id || undefined },
});
},
});
},
});
{
timestamp: 2021-02-19T09:43:06.343Z,
query: 'SELECT `dev`.`Post`.`id`, `dev`.`Post`.`createdAt`, `dev`.`Post`.`updatedAt`, `dev`.`Post`.`title`, `dev`.`Post`.`content`, `dev`.`Post`.`published`, `dev`.`Post`.`viewCount`, `dev`.`Post`.`authorId` FROM `dev`.`Post` WHERE `dev`.`Post`.`authorId` = ? LIMIT ? OFFSET ?',
params: '[1,-1,0]',
duration: 0,
target: 'quaint::connector::metrics'
}
{
timestamp: 2021-02-19T09:43:06.347Z,
query: 'SELECT `dev`.`Post`.`id`, `dev`.`Post`.`createdAt`, `dev`.`Post`.`updatedAt`, `dev`.`Post`.`title`, `dev`.`Post`.`content`, `dev`.`Post`.`published`, `dev`.`Post`.`viewCount`, `dev`.`Post`.`authorId` FROM `dev`.`Post` WHERE `dev`.`Post`.`authorId` = ? LIMIT ? OFFSET ?',
params: '[3,-1,0]',
duration: 0,
target: 'quaint::connector::metrics'
}
{
timestamp: 2021-02-19T09:43:06.348Z,
query: 'SELECT `dev`.`Post`.`id`, `dev`.`Post`.`createdAt`, `dev`.`Post`.`updatedAt`, `dev`.`Post`.`title`, `dev`.`Post`.`content`, `dev`.`Post`.`published`, `dev`.`Post`.`viewCount`, `dev`.`Post`.`authorId` FROM `dev`.`Post` WHERE `dev`.`Post`.`authorId` = ? LIMIT ? OFFSET ?',
params: '[2,-1,0]',
duration: 0,
target: 'quaint::connector::metrics'
}
{
timestamp: 2021-02-19T09:43:06.348Z,
query: 'SELECT `dev`.`Post`.`id`, `dev`.`Post`.`createdAt`, `dev`.`Post`.`updatedAt`, `dev`.`Post`.`title`, `dev`.`Post`.`content`, `dev`.`Post`.`published`, `dev`.`Post`.`viewCount`, `dev`.`Post`.`authorId` FROM `dev`.`Post` WHERE `dev`.`Post`.`authorId` = ? LIMIT ? OFFSET ?',
params: '[4,-1,0]',
duration: 0,
target: 'quaint::connector::metrics'
}
{
timestamp: 2021-02-19T09:43:06.348Z,
query: 'SELECT `dev`.`Post`.`id`, `dev`.`Post`.`createdAt`, `dev`.`Post`.`updatedAt`, `dev`.`Post`.`title`, `dev`.`Post`.`content`, `dev`.`Post`.`published`, `dev`.`Post`.`viewCount`, `dev`.`Post`.`authorId` FROM `dev`.`Post` WHERE `dev`.`Post`.`authorId` = ? LIMIT ? OFFSET ?',
params: '[5,-1,0]',
duration: 0,
target: 'quaint::connector::metrics'
}
// And so on
Use findUnique() in combination with the fluent API (.posts()) as shown to return a user's posts. Even though the resolver is called once per user, the Prisma dataloader in Prisma Client ✔ batches the findUnique() queries.
:::info
It may seem counterintuitive to use a prisma.user.findUnique(...).posts() query to return posts instead of prisma.posts.findMany() - particularly as the former results in two queries rather than one.
The only reason you need to use the fluent API (user.findUnique(...).posts()) to return posts is that the dataloader in Prisma Client batches findUnique() queries and does not currently batch findMany() queries.
When the dataloader batches findMany() queries or your query has the relationStrategy set to join, you no longer need to use findUnique() with the fluent API in this way.
:::
const User = objectType({
name: "User",
definition(t) {
t.nonNull.int("id");
t.string("name");
t.nonNull.string("email");
t.nonNull.list.nonNull.field("posts", {
type: "Post",
resolve: (parent, _, context) => {
return context.prisma.post.findMany({
// [!code --]
where: { authorId: parent.id || undefined }, // [!code --]
}); // [!code --]
return context.prisma.user // [!code ++]
.findUnique({
// [!code ++]
where: { id: parent.id || undefined }, // [!code ++]
}) // [!code ++]
.posts(); // [!code ++]
}, // [!code ++]
});
},
});
{
timestamp: 2021-02-19T09:59:46.340Z,
query: 'SELECT `dev`.`User`.`id`, `dev`.`User`.`email`, `dev`.`User`.`name` FROM `dev`.`User` WHERE 1=1 LIMIT ? OFFSET ?',
params: '[-1,0]',
duration: 0,
target: 'quaint::connector::metrics'
}
{
timestamp: 2021-02-19T09:59:46.350Z,
query: 'SELECT `dev`.`User`.`id` FROM `dev`.`User` WHERE `dev`.`User`.`id` IN (?,?,?) LIMIT ? OFFSET ?',
params: '[1,2,3,-1,0]',
duration: 0,
target: 'quaint::connector::metrics'
}
{
timestamp: 2021-02-19T09:59:46.350Z,
query: 'SELECT `dev`.`Post`.`id`, `dev`.`Post`.`createdAt`, `dev`.`Post`.`updatedAt`, `dev`.`Post`.`title`, `dev`.`Post`.`content`, `dev`.`Post`.`published`, `dev`.`Post`.`viewCount`, `dev`.`Post`.`authorId` FROM `dev`.`Post` WHERE `dev`.`Post`.`authorId` IN (?,?,?) LIMIT ? OFFSET ?',
params: '[1,2,3,-1,0]',
duration: 0,
target: 'quaint::connector::metrics'
}
If the posts resolver is invoked once per user, the dataloader in Prisma Client groups findUnique() queries with the same parameters and selection set. Each group is optimized into a single findMany().
You can perform the query with a database join by setting relationLoadStrategy to "join", ensuring that only one query is executed against the database.
const User = objectType({
name: "User",
definition(t) {
t.nonNull.int("id");
t.string("name");
t.nonNull.string("email");
t.nonNull.list.nonNull.field("posts", {
type: "Post",
resolve: (parent, _, context) => {
return context.prisma.post.findMany({
relationLoadStrategy: "join",
where: { authorId: parent.id || undefined },
});
},
});
},
});
Don't loop with separate queries:
// BAD: n+1 queries
const users = await prisma.user.findMany({});
users.forEach(async (usr) => {
const posts = await prisma.post.findMany({ where: { authorId: usr.id } });
});
Use include or in filter instead:
// GOOD: 2 queries with include
const usersWithPosts = await prisma.user.findMany({
include: { posts: true },
});
// GOOD: 2 queries with in filter
const users = await prisma.user.findMany({});
const posts = await prisma.post.findMany({
where: { authorId: { in: users.map(u => u.id) } },
});
// BEST: 1 query with join
const posts = await prisma.post.findMany({
relationLoadStrategy: "join",
where: { authorId: { in: users.map(u => u.id) } },
});
This is not an efficient way to query. Instead, you can:
include ) to return users and related postsin filterrelationLoadStrategy to "join"includeYou can use include to return each user's posts. This only results in two SQL queries - one to get users, and one to get posts. This is known as a nested read.
const usersWithPosts = await prisma.user.findMany({
include: {
posts: true,
},
});
SELECT "public"."User"."id", "public"."User"."email", "public"."User"."name" FROM "public"."User" WHERE 1=1 OFFSET $1
SELECT "public"."Post"."id", "public"."Post"."title", "public"."Post"."authorId" FROM "public"."Post" WHERE "public"."Post"."authorId" IN ($1,$2,$3,$4) OFFSET $5
inIf you have a list of user IDs, you can use the in filter to return all posts where the authorId is in that list of IDs:
const users = await prisma.user.findMany({});
const userIds = users.map((x) => x.id);
const posts = await prisma.post.findMany({
where: {
authorId: {
in: userIds,
},
},
});
SELECT "public"."User"."id", "public"."User"."email", "public"."User"."name" FROM "public"."User" WHERE 1=1 OFFSET $1
SELECT "public"."Post"."id", "public"."Post"."createdAt", "public"."Post"."updatedAt", "public"."Post"."title", "public"."Post"."content", "public"."Post"."published", "public"."Post"."authorId" FROM "public"."Post" WHERE "public"."Post"."authorId" IN ($1,$2,$3,$4) OFFSET $5
relationLoadStrategy: "join"You can perform the query with a database join by setting relationLoadStrategy to "join", ensuring that only one query is executed against the database.
const users = await prisma.user.findMany({});
const userIds = users.map((x) => x.id);
const posts = await prisma.post.findMany({
relationLoadStrategy: "join",
where: {
authorId: {
in: userIds,
},
},
});