Вступить в клуб →
средняявопросPostgreSQL, ORM и кэш Redis

Потерянное обновление баланса при параллельных списаниях

Списание бонусов реализовано так:

await prisma.$transaction(async (tx) => {
  const acc = await tx.account.findUnique({ where: { id } });
  const next = acc.balance - amount;
  if (next < 0) throw new NotEnoughBonuses();
  await tx.account.update({ where: { id }, data: { balance: next } });
});

В логах видно, что при двух почти одновременных списаниях по 100 у пользователя со счётом 100 обе операции проходят успешно и баланс становится 0, хотя списано 200 — второе списание должно было получить отказ. Что происходит?

  • Обе транзакции прочитали баланс до записи и обе записали 0; нужна блокировка строки или атомарный UPDATE с условием
  • Ошибка в проверке: сравнивать нужно `acc.balance < amount` до вычитания, тогда второе списание отсечётся на проверке
  • Prisma не поддерживает интерактивные транзакции такого вида: колбэк выполняется вне транзакции, нужен сырой SQL
  • Проблема в кэше сущностей ORM: второй запрос получил устаревшее значение из кэша, его нужно отключить для счетов

🔒 Проверка ответа — для участников клуба

  • Проверка ответа
  • Подсказка, если застряли
  • Разбор с объяснением, почему так
  • Прогресс по всем задачам и виртуальные собеседования
Зарегистрироваться →

Регистрация занимает минуту

Другие задачи раздела