Потерянное обновление баланса при параллельных списаниях
Списание бонусов реализовано так:
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: второй запрос получил устаревшее значение из кэша, его нужно отключить для счетов
