PrismaMiddleTechnical
Какие isolation levels и transaction options поддерживает Prisma и когда они важны?
Prisma поддерживает явное указание isolation level для интерактивных транзакций через параметр isolationLevel. Доступны ReadUncommitted, ReadCommitted, RepeatableRead, Snapshot и Serializable — набор зависит от провайдера БД.
Уровни изоляции в Prisma
Prisma позволяет задавать уровень изоляции только для интерактивных транзакций ($transaction с callback). Batch-транзакции используют уровень изоляции по умолчанию провайдера.
Доступные уровни изоляции
- ReadUncommitted — видит незафиксированные изменения других транзакций (dirty read). PostgreSQL игнорирует этот уровень, повышая до ReadCommitted.
- ReadCommitted — видит только зафиксированные данные. Default для PostgreSQL и SQL Server.
- RepeatableRead — повторное чтение тех же строк даёт одинаковый результат. Защищает от non-repeatable read. Default для MySQL InnoDB.
- Snapshot — только SQL Server. Читает из снимка на начало транзакции.
- Serializable — полная сериализуемость, максимальная защита. Высокая вероятность deadlock/retry.
Пример использования
import { Prisma } from '@prisma/client';
// Перевод средств — нужен Serializable для защиты от race condition
async function transferFunds(
fromId: number,
toId: number,
amount: number
) {
return prisma.$transaction(
async (tx) => {
const from = await tx.account.findUniqueOrThrow({
where: { id: fromId },
select: { balance: true },
});
if (from.balance < amount) {
throw new Error('Insufficient balance');
}
const [updatedFrom, updatedTo] = await Promise.all([
tx.account.update({
where: { id: fromId },
data: { balance: { decrement: amount } },
}),
tx.account.update({
where: { id: toId },
data: { balance: { increment: amount } },
}),
]);
return { from: updatedFrom, to: updatedTo };
},
{
isolationLevel: Prisma.TransactionIsolationLevel.Serializable,
maxWait: 5000, // мс ожидания соединения из пула
timeout: 10000, // мс максимального времени транзакции
}
);
}
Когда какой уровень использовать
- ReadCommitted — большинство CRUD-операций, чтение агрегатов без строгих требований к консистентности.
- RepeatableRead — отчёты, где нужно гарантировать неизменность прочитанных данных в рамках одной транзакции.
- Serializable — финансовые операции, инвентаризация (списание остатков), любые операции типа «прочитал-проверил-записал».
Retry при Serializable
Serializable-транзакции могут завершаться ошибкой P2034 (serialization failure) — PostgreSQL откатывает транзакцию при конфликте. Нужен retry-механизм:
async function withRetry<T>(
fn: () => Promise<T>,
maxRetries = 3
): Promise<T> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (e) {
if (
e instanceof Prisma.PrismaClientKnownRequestError &&
e.code === 'P2034' &&
attempt < maxRetries - 1
) {
// Exponential backoff
await new Promise(r => setTimeout(r, 50 * 2 ** attempt));
continue;
}
throw e;
}
}
throw new Error('Max retries exceeded');
}
// Использование
const result = await withRetry(() => transferFunds(1, 2, 100));
Подводные камни
- PostgreSQL не поддерживает ReadUncommitted — тихо повышает до ReadCommitted, что может скрыть баги при тестировании на MySQL.
- Serializable в PostgreSQL использует SSI (Serializable Snapshot Isolation) — это не блокировки, а оптимистичная проверка. Высокая конкуренция = частые откаты P2034.
- Уровень изоляции нельзя задать для batch-транзакций (
$transaction([op1, op2])) — только для callback-формы. - Длинные Serializable-транзакции с HTTP-запросами внутри — антипаттерн: держат снимки данных и конфликтуют с конкурентными транзакциями.
- MySQL в RepeatableRead по умолчанию + MVCC может вести себя иначе, чем PostgreSQL — тесты должны запускаться на той же СУБД, что используется в production.
- Timeout транзакции (
timeoutопция) — это таймаут Prisma, не PostgreSQL.statement_timeoutиlock_timeoutнужно настраивать отдельно через$executeRaw.
Common mistakes
- Отвечать определением без production-сценария.
- Не называть runtime boundary, security boundary или failure mode.
- Игнорировать версию API, observability и тестовую проверку.
What the interviewer is testing
- Объясняет механизм своими словами и без выдуманных API.
- Называет реальные риски, диагностику и критерий корректности.
- Связывает ответ с текущей документацией и миграционными ограничениями.