PrismaMiddleExperience

Какие ошибки делают команды, когда прячут сложность базы данных за Prisma?

Главные ошибки: N+1 при итерации с запросами внутри цикла, SELECT * без явного select, блокирующие миграции без CONCURRENTLY, отсутствие транзакций там, где они нужны, и нехватка индексов на полях фильтрации.

Когда Prisma становится абстракцией над хаосом

Prisma отлично снижает порог входа — не нужно писать SQL руками, схема читается как документация, типы генерируются автоматически. Но именно эта лёгкость провоцирует паттерны, которые превращают базу данных в узкое место в продакшене.

Ошибка 1: игнорирование N+1

Самая распространённая проблема. Разработчик пишет:

const users = await prisma.user.findMany();
for (const user of users) {
  const posts = await prisma.post.findMany({
    where: { authorId: user.id },
  });
}

100 пользователей = 101 запрос к БД. Prisma не объединяет эти запросы автоматически. Правильно — использовать include или select с вложенными связями:

const users = await prisma.user.findMany({
  include: {
    posts: true,
  },
});

Но даже include не всегда генерирует JOIN — Prisma может делать отдельный SELECT для связей типа «один-ко-многим». Это нужно проверять через лог.

Ошибка 2: SELECT * везде

По умолчанию Prisma выбирает все поля. Если в таблице есть avatar bytea, description text или JSON-колонки, они тянутся при каждом запросе. В API, который возвращает список из 50 строк, это могут быть мегабайты лишних данных.

// Плохо
const users = await prisma.user.findMany();

// Хорошо
const users = await prisma.user.findMany({
  select: {
    id: true,
    name: true,
    email: true,
  },
});

Ошибка 3: миграции без анализа плана выполнения

Команды запускают prisma migrate deploy в продакшене и не думают о блокировках. Добавление NOT NULL колонки без DEFAULT блокирует таблицу. Создание индекса через миграцию без CONCURRENTLY блокирует запись.

-- Prisma сгенерирует это:
CREATE INDEX "User_email_idx" ON "User"("email");

-- Нужно вручную заменить на:
CREATE INDEX CONCURRENTLY "User_email_idx" ON "User"("email");

Prisma не поддерживает CONCURRENTLY в схеме — это нужно делать через raw SQL в кастомных миграциях.

Ошибка 4: транзакции везде и нигде

Два крайних случая: либо весь код без транзакций (данные в несогласованном состоянии при сбое в середине операции), либо гигантские транзакции, которые держат блокировки секундами. Prisma не навязывает транзакционную модель — разработчик должен думать о границах сам.

// Без транзакции — опасно
await prisma.order.create({ data: orderData });
await prisma.inventory.update({ where: { id }, data: { stock: { decrement: 1 } } });

// С транзакцией
await prisma.$transaction([
  prisma.order.create({ data: orderData }),
  prisma.inventory.update({ where: { id }, data: { stock: { decrement: 1 } } }),
]);

Ошибка 5: отсутствие индексов на часто используемых полях фильтрации

Prisma генерирует WHERE по любому полю — но индекса там нет, если он не указан в схеме явно. Многие команды не добавляют @@index вовремя:

model User {
  id        String   @id @default(cuid())
  email     String   @unique
  teamId    String
  createdAt DateTime @default(now())

  // Забыли добавить:
  @@index([teamId, createdAt])
}

Ошибка 6: использование Prisma для аналитических запросов

Агрегации, оконные функции, CTE — Prisma поддерживает только базовые groupBy и aggregate. Команды пытаются реализовать аналитику через JS вместо SQL, что убивает производительность. Правильно — использовать $queryRaw для сложных запросов.

Подводные камни

  • Prisma не предупреждает об N+1 в runtime — нужно явно включать логирование и смотреть количество запросов.
  • Миграции Prisma в продакшене могут блокировать таблицы — нужен review каждого SQL в папке migrations/ перед деплоем.
  • Отсутствие connection pooling на уровне приложения при serverless деплое приводит к исчерпанию соединений PostgreSQL — нужен Prisma Accelerate или pgBouncer.
  • prisma.$transaction с callback (interactive transaction) держит соединение всё время выполнения — долгие операции внутри приводят к пулу исчерпания.
  • Soft delete через middleware — популярный паттерн, но легко забыть применить его везде, особенно в include-запросах.
  • Prisma не поддерживает partial indexes и expression indexes в schema.prisma — их нужно добавлять вручную в кастомных миграциях.
  • Команды путают скорость разработки с производительностью — Prisma ускоряет первые, но не гарантирует вторую.

What hurts your answer

  • Перечислять ошибки без объяснения причин
  • Не отличать beginner mistakes от production failure modes
  • Не предлагать процесс, который предотвращает повторение ошибок

What they're listening for

  • Знает типичные ошибки при работе с Prisma
  • Понимает причины ошибок
  • Предлагает практики prevention и early detection

Related topics

Какие ошибки делают команды, когда прячут сложность базы данных за Prisma? | Talanto