Движок Prisma добавляет накладные издержки примерно в 2–7 раз даже в версии 7. В этом руководстве показано, как приблизиться к производительности «чистого SQL», сохранив удобство разработки и типизацию Prisma.
Проблема: почему Prisma медленнее «чистого SQL» Prisma — удобная ORM, но каждый запрос проходит через несколько уровней обработки:
Ваш код
↓
Prisma Client (TypeScript)
↓
Query Engine (бинарник на Rust)
↓
Генерация SQL
↓
Валидация запроса
↓
Сериализация результата
↓
Преобразование типов
↓
База данных
Каждый уровень добавляет задержку. Улучшения в Prisma v7 не устраняют фундаментальные ограничения этой архитектуры.
Практический эффект:
// Prisma v7: ~0.34 ms накладных издержек
const users = await prisma.user.findMany({
where: { status: 'ACTIVE' }
})
// Чистый SQL: ~0.17 ms накладных издержек
const users = await sql`
SELECT * FROM users WHERE status = 'ACTIVE'
`
Для высоконагруженных API, которые обрабатывают тысячи запросов в секунду, разница в 0.17 ms на запрос быстро превращается в заметную прибавку к общей задержке.
Решение: выполнять SQL напрямую Единственный способ добиться производительности «чистого SQL» — обойти Query Engine Prisma, но при этом сохранить привычный API Prisma клиента.
Сравнение архитектур Обычная Prisma:
prisma.user.findMany()
→ Query Engine (Rust)
→ Генерация SQL
→ База данных
→ Обработка результата
→ Возврат
Оптимизированный подход:
prisma.user.findMany()
→ Генерация SQL (TypeScript)
→ postgres.js / better-sqlite3
→ База данных
→ Возврат (в том же формате)
Если генерировать SQL прямо из JSON-запроса и выполнять его через проверенные драйверы, накладные расходы Query Engine исчезают. Процесс подготовки становится обычным превращением JSON в строку.
Реализация 1) Установка зависимостей PostgreSQL:
npm install prisma-sql postgres
SQLite:
npm install prisma-sql better-sqlite3
2) Базовая настройка (runtime-режим) PostgreSQL:
import { PrismaClient, Prisma } from '@prisma/client'
import { speedExtension, convertDMMFToModels } from 'prisma-sql'
import postgres from 'postgres'
// Конвертируем схему Prisma во внутренний формат (один раз)
const models = convertDMMFToModels(Prisma.dmmf.datamodel)
// Создаём postgres.js клиент
const sql = postgres(process.env.DATABASE_URL)
// Расширяем Prisma клиент
const prisma = new PrismaClient().$extends(
speedExtension({
postgres: sql,
models
})
)
// Используем как обычно — чтение станет быстрее в 2–7 раз
const users = await prisma.user.findMany({
where: { status: 'ACTIVE' },
include: { posts: true }
})
SQLite:
import { PrismaClient, Prisma } from '@prisma/client'
import { speedExtension, convertDMMFToModels } from 'prisma-sql'
import Database from 'better-sqlite3'
const models = convertDMMFToModels(Prisma.dmmf.datamodel)
const db = new Database('./data.db')
const prisma = new PrismaClient().$extends(
speedExtension({
sqlite: db,
models
})
)
const users = await prisma.user.findMany({
where: { status: 'ACTIVE' }
})
Готово. Код запросов менять не нужно. Все операции чтения теперь выполняются быстрее.
3) Продвинутый режим: generator (предварительная генерация SQL) Для максимальной производительности можно «запечь» самые частые запросы на этапе сборки.
Добавьте в schema.prisma:
generator sql {
provider = "prisma-sql-generator"
}
/// @optimize {
/// "method": "findMany",
/// "query": {
/// "where": { "status": "ACTIVE" },
/// "orderBy": { "createdAt": "desc" },
/// "take": "$take",
/// "skip": "$skip"
/// }
/// }
/// @optimize {
/// "method": "count",
/// "query": { "where": { "status": "ACTIVE" } }
/// }
model User {
id Int @id @default(autoincrement())
email String @unique
status String
createdAt DateTime @default(now())
posts Post[]
}
Сгенерируйте код:
Подключите сгенерированное расширение:
import { PrismaClient } from '@prisma/client'
import { createExtension } from './generated/sql'
import postgres from 'postgres'
const sql = postgres(process.env.DATABASE_URL)
const prisma = new PrismaClient().$extends(
createExtension({ postgres: sql })
)
// ⚡ использует заранее созданный SQL (~0.03 ms накладных издержек)
const activeUsers = await prisma.user.findMany({
where: { status: 'ACTIVE' },
orderBy: { createdAt: 'desc' },
take: 10,
skip: 0
})
// 🔧 RUNTIME: генерирует SQL «на лету» (~0.2 ms накладных издержек)
const searchUsers = await prisma.user.findMany({
where: { email: { contains: '@example.com' } }
})
Сравнение накладных издержек:
Prisma v7: ~0.34 ms Runtime-режим: ~0.20 ms (примерно в 1.7 раза быстрее) Generator-режим: ~0.03 ms (примерно в 11 раз быстрее) Результаты бенчмарков Тесты на 137 E2E-запросах, сравнивающих идентичные операции.
PostgreSQL В среднем: в 2.10 раза быстрее, чем Prisma v7
SQLite В среднем: в 5.48 раза быстрее, чем Prisma v7
Конфигурация для production 1) Режим отладки Можно видеть сгенерированный SQL для каждого запроса:
const prisma = new PrismaClient().$extends(
speedExtension({
postgres: sql,
models,
debug: true // Логирует SQL для отладки
})
)
// Пример вывода:
// [postgres] User.findMany
// SQL: SELECT * FROM users WHERE status = $1
// Params: ['ACTIVE']
2) Мониторинг производительности Можно отслеживать время выполнения запросов в production:
const slowQueries: Array<{ query: string; duration: number }> = []
const prisma = new PrismaClient().$extends(
speedExtension({
postgres: sql,
models,
onQuery: (info) => {
// Логируем медленные запросы
if (info.duration > 100) {
slowQueries.push({
query: `${info.model}.${info.method}`,
duration: info.duration
})
// Критически медленный запрос
if (info.duration > 500) {
logger.error('Критически медленный запрос', {
model: info.model,
method: info.method,
sql: info.sql,
duration: info.duration
})
}
}
// Метрики
metrics.histogram('db.query.duration', info.duration, {
model: info.model,
method: info.method,
prebaked: info.prebaked
})
}
})
)
3) Выборочная оптимизация Можно ускорять только «горячие» модели:
const prisma = new PrismaClient().$extends(
speedExtension({
postgres: sql,
models,
allowedModels: ['User', 'Post', 'Order'] // Только горячие пути
})
)
// Быстро (оптимизировано)
await prisma.user.findMany()
await prisma.post.findMany()
// Обычная Prisma (откат к стандартному поведению)
await prisma.auditLog.findMany()
4) Настройка пула соединений Пример конфигурации postgres.js для production:
const sql = postgres(process.env.DATABASE_URL, {
max: 20, // Размер пула соединений
idle_timeout: 20, // Закрывать idle-соединения через 20 секунд
connect_timeout: 10, // Быстро падать при проблемах с подключением
prepare: true, // Использовать prepared statements
transform: {
undefined: null // Преобразовывать undefined в NULL
},
ssl: process.env.NODE_ENV === 'production'
? { rejectUnauthorized: false }
: undefined
})
Поддерживаемые возможности ✅ Что ускоряется (выполняется через «чистый SQL») Запросы:
findMany, findFirst, findUnique count aggregate (_count, _sum, _avg, _min, _max) groupBy с условиями having Фильтры:
// Сравнения
{ age: { gt: 18, lte: 65 } }
{ status: { in: ['ACTIVE', 'PENDING'] } }
{ status: { not: 'DELETED' } }
// Строковые операции
{ email: { contains: '@example.com' } }
{ email: { startsWith: 'user' } }
{ email: { endsWith: '.com' } }
{ name: { contains: 'John', mode: 'insensitive' } }
// Булева логика
{ AND: [{ status: 'ACTIVE' }, { verified: true }] }
{ OR: [{ role: 'ADMIN' }, { role: 'MODERATOR' }] }
{ NOT: { status: 'DELETED' } }
// Проверки на null
{ deletedAt: null }
{ deletedAt: { not: null } }
Связи:
// include
{
include: {
posts: {
where: { published: true },
orderBy: { createdAt: 'desc' },
take: 5
},
profile: true
}
}
// Вложенные include
{
include: {
posts: {
include: {
comments: {
include: { author: true }
}
}
}
}
}
// Фильтры по связям
{
where: {
posts: { some: { published: true } },
comments: { none: { flagged: true } }
}
}
Пагинация и сортировка:
// Offset-пагинация
{ take: 10, skip: 20, orderBy: { createdAt: 'desc' } }
// Курсорная пагинация
{
cursor: { id: 100 },
take: 10,
skip: 1,
orderBy: { id: 'asc' }
}
// Мультисортировка
{
orderBy: [
{ status: 'asc' },
{ priority: 'desc' },
{ createdAt: 'desc' }
]
}
❌ Что остаётся на стандартной Prisma (без изменений) Запись:
create, update, delete, upsert createMany, updateMany, deleteMany Продвинутые возможности:
Транзакции ($transaction) Сырые запросы ($queryRaw, $executeRaw) Middleware Другие расширения Prisma Client Поддержка Edge Runtime Vercel Edge Functions import { PrismaClient, Prisma } from '@prisma/client'
import { speedExtension, convertDMMFToModels } from 'prisma-sql'
import postgres from 'postgres'
const models = convertDMMFToModels(Prisma.dmmf.datamodel)
const sql = postgres(process.env.DATABASE_URL)
const prisma = new PrismaClient().$extends(
speedExtension({ postgres: sql, models })
)
export const config = { runtime: 'edge' }
export default async function handler(req: Request) {
const users = await prisma.user.findMany({
where: { status: 'ACTIVE' }
})
return Response.json(users)
}
Cloudflare Workers import { createToSQL, convertDMMFToModels } from 'prisma-sql'
import { Prisma } from '@prisma/client'
const models = convertDMMFToModels(Prisma.dmmf.datamodel)
const toSQL = createToSQL(models, 'sqlite')
export default {
async fetch(request: Request, env: Env) {
// Генерируем SQL
const { sql, params } = toSQL('User', 'findMany', {
where: { status: 'ACTIVE' }
})
// Выполняем через D1
const result = await env.DB.prepare(sql)
.bind(...params)
.all()
return Response.json(result.results)
}
}
Руководство по миграции Со стандартной Prisma Было:
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
const users = await prisma.user.findMany({
where: { status: 'ACTIVE' },
include: { posts: true }
})
Стало:
import { PrismaClient, Prisma } from '@prisma/client'
import { speedExtension, convertDMMFToModels } from 'prisma-sql'
import postgres from 'postgres'
const models = convertDMMFToModels(Prisma.dmmf.datamodel)
const sql = postgres(process.env.DATABASE_URL)
const prisma = new PrismaClient().$extends(
speedExtension({ postgres: sql, models })
)
// Тот же код — просто быстрее
const users = await prisma.user.findMany({
where: { status: 'ACTIVE' },
include: { posts: true }
})
С Drizzle ORM Было:
import { drizzle } from 'drizzle-orm/postgres-js'
import postgres from 'postgres'
import { users } from './schema'
import { eq } from 'drizzle-orm'
const sql = postgres(DATABASE_URL)
const db = drizzle(sql)
const activeUsers = await db
.select()
.from(users)
.where(eq(users.status, 'ACTIVE'))
Стало:
import { PrismaClient, Prisma } from '@prisma/client'
import { speedExtension, convertDMMFToModels } from 'prisma-sql'
import postgres from 'postgres'
const models = convertDMMFToModels(Prisma.dmmf.datamodel)
const sql = postgres(DATABASE_URL)
const prisma = new PrismaClient().$extends(
speedExtension({ postgres: sql, models })
)
// Более читаемый код при сопоставимой производительности
const activeUsers = await prisma.user.findMany({
where: { status: 'ACTIVE' }
})
Частые проблемы и решения 1)
speedExtension requires models parameter
Проблема:
// ❌ Неправильно
const prisma = new PrismaClient().$extends(
speedExtension({ postgres: sql })
)
Решение:
// ✅ Правильно
import { Prisma } from '@prisma/client'
import { convertDMMFToModels } from 'prisma-sql'
const models = convertDMMFToModels(Prisma.dmmf.datamodel)
const prisma = new PrismaClient().$extends(
speedExtension({ postgres: sql, models })
)
2) Исчерпан пул соединений Проблема:
Error: Connection pool exhausted
Решение:
const sql = postgres(DATABASE_URL, {
max: 50, // Увеличьте значение (по умолчанию часто 10)
idle_timeout: 20
})
3) Результаты отличаются от стандартной Prisma Проблема:
// Результаты Prisma и оптимизированной версии отличаются
Решение:
// Включите отладку
const prisma = new PrismaClient().$extends(
speedExtension({
postgres: sql,
models,
debug: true
})
)
// Сравните с логом Prisma
const originalPrisma = new PrismaClient({
log: ['query']
})
Если результаты действительно расходятся, создайте issue: https://github.com/multipliedtwice/prisma-sql/issues
4) Производительность почти не выросла Проблема: ожидалось ускорение в 2–7 раз, но прирост минимальный.
Возможные причины:
Доминирует сеть: на удалённой БД задержка сети (10–50 ms) «затмевает» накладные издержки (0.2 ms) Очень простые запросы: findUnique по индексированному ID и так быстрый Нет индексов: запросы упираются в план выполнения БД Что сделать:
const prisma = new PrismaClient().$extends(
speedExtension({
postgres: sql,
models,
onQuery: (info) => {
console.log({
query: `${info.model}.${info.method}`,
duration: info.duration,
prebaked: info.prebaked
})
}
})
)
Когда это не стоит применять Оставьте стандартную Prisma, если: Преобладает запись: больше 50% операций — запись Простой CRUD: одна таблица, минимум связей Команда не готова: нет опыта с postgres.js / better-sqlite3 Нужны неподдерживаемые функции Prisma: активное использование возможностей, которые здесь не покрыты Этот подход подходит, когда: Преобладает чтение: более 70% операций — чтение Высокая пропускная способность: >1000 rps, где важны миллисекунды Сложные запросы: много связей, агрегаций, условий Оптимизация затрат: меньше задержка → меньше ресурсов Заключение Можно добиться 100% производительности «чистого SQL», не отказываясь от удобства Prisma.
Основные преимущества:
Ускорение чтения в 2–7 раз без переписывания запросов Те же типы и тот же API Prisma Client Подходит для production (по заявлению авторов) и покрыто E2E-тестами Работает в существующих проектах Prisma Быстрый старт:
Установите prisma-sql и postgres/better-sqlite3 Подключите расширение Задеплойте и измерьте эффект Дальнейшая оптимизация:
Подключите generator для «горячих» запросов Включите мониторинг через onQuery Настройте пул соединений Ресурсы: