Performance
Prisma Intermediate/Advanced
Chapter 7 ยท Performance
Prisma makes queries easy to write, which also makes slow ones easy to write. Almost every Prisma performance problem is one of a few familiar mistakes: too many queries, too much data, a missing index, or too few (or too many) database connections. This chapter shows how to spot each one and what to do about it. The rule throughout: measure first, then fix what the measurements show.
Step 1: See the SQL
Every SQL statement Prisma sends is now printed with its duration. Chapter 6's slow-query extension times whole
Prisma operations; this log shows the individual statements underneath, which is what you need to spot the
problems below. Keep it on in development and off (or sampled) in production, because it's noisy and the
params field can contain personal data.
Problem 1: Too Many Queries (N+1)
The first version sends 51 queries for 50 posts. In the query log it's unmistakable: the same statement repeated
over and over with a different ID. The fix is to load related data with include or a nested
select, as in Prisma Fundamentals, Chapter 9.
Joins instead of extra queries
By default, include fetches each level of relations with a separate query using
WHERE id IN (...). On PostgreSQL and MySQL, the relationJoins preview feature can load
them in a single query with database joins instead:
Neither strategy is always faster — one round trip versus several simpler queries. If a page is slow and loads several levels of relations, try both and compare the timings in your log.
Problem 2: Too Much Data
| Habit | Cost | Better |
|---|---|---|
| findMany() with no select | Every column, including long content text | select only the fields the page shows |
| include: { posts: true } | Every related row, unbounded | Add take, where and orderBy inside the include |
| No take on a list endpoint | The whole table as it grows | Always paginate |
| Large skip values | The database still reads and discards skipped rows | Cursor pagination for deep pages |
| Counting by fetching | Rows sent just to call .length | count() or relation _count (Chapter 2) |
Problem 3: Missing Indexes
An index lets the database find matching rows without reading the whole table. Prisma creates indexes for
@id and @unique fields. For other columns you filter or sort on often, add one:
Then run prisma migrate dev to create it. To check whether a query actually uses an index, ask the
database directly with EXPLAIN ANALYZE (PostgreSQL), using Chapter 3's raw SQL:
Problem 4: Connections
Each PrismaClient keeps a pool of open database connections and reuses them. In
Prisma 7, the pool belongs to the database driver behind your adapter, and so do its settings:
?connection_limit=5&pool_timeout=10 to the database URL describe Prisma 6's
built-in pool. With driver adapters, set pool options on the adapter instead. Prisma's upgrade guide also notes
that pg has no connection timeout by default, where Prisma 6 used 5 seconds; the values above restore
Prisma 6's timeouts.
The most common connection mistake
This opens fresh connections for every request and quickly exhausts the database's connection limit. Create one
client when the app starts and share it — exactly what lib/prisma.ts from Prisma Fundamentals
does. Pool size is a balance: too small and requests queue waiting for a connection; too large, multiplied by
every running copy of your app, and you exceed what the database allows. Serverless platforms make this harder,
which Chapter 9 covers.
Problem 5: Doing Work One Row at a Time
| Instead of | Use |
|---|---|
A loop of create calls | createMany (Chapter 4) |
A loop of update calls with the same change | updateMany with a where |
| Read a value, add 1, write it back | { increment: 1 }, one atomic statement |
| Independent queries awaited one after another | Promise.all or $transaction([...]) |
| Complex bulk changes Prisma can't express | One raw SQL statement (Chapter 3) |
A Performance Checklist
- Turn on the query log and reproduce the slow request.
- Many near-identical statements? Fix the N+1 with
include/select. - Large rows or unbounded lists? Add
select,takeand pagination. - One slow statement?
EXPLAIN ANALYZEit and consider an index. - Fast queries but slow requests under load? Look at the pool size and timeouts.
- Measure again to confirm the fix actually helped.
Hands-On Exercises
An endpoint returns each tag with its three most-viewed published posts, but it loops over tags and runs a query per tag. Use the query log to count its queries, then rewrite it to use a fixed number of queries.
๐ View solutionSeed 100,000 posts, then use EXPLAIN ANALYZE to compare "latest 20 published posts" before and after adding @@index([published, createdAt(sort: Desc)]). Describe what changed in the plan.
Your API runs as 4 copies, each with a pool of 10, and the database allows 50 connections. Is that safe? What changes when you add a migration job, an admin script and a fifth copy? Configure the adapter accordingly.
๐ View solutionChapter 7 Quick Reference
- Measure first:
log: [{ emit: "event", level: "query" }]and$on("query", e => e.duration) - N+1: repeated identical statements; fix with
include/nestedselect relationLoadStrategy: "join"(previewrelationJoins) vs. the default"query"— measure both- Fetch less:
select, bounded includes,take, cursor pagination,count() @@indexfor frequent filters/sorts; confirm withEXPLAIN ANALYZE- Prisma 7 pools live in the driver: set
maxand timeouts on the adapter; one shared client per app - Batch:
createMany,updateMany,increment,Promise.all