Выбор ответа Senior Eloquent, N+1 и миграции обновлено

Гонка при списании баланса

Вопрос

Два запроса одновременно списывают деньги с одного счёта: читают баланс, проверяют, уменьшают и сохраняют. Как исправить в Laravel?

Попробуйте сами

Выберите вариант, и откроется разбор.

Показать ответ и разбор

Коротко

Правильный ответ: В транзакции lockForUpdate() или атомарный условный UPDATE ... WHERE. Без блокировки оба запроса читают одинаковый баланс, оба проходят проверку, и списание происходит дважды (lost update).

Варианты ответа

  1. В транзакции lockForUpdate() или атомарный условный UPDATE ... WHEREправильный ответ
  2. Обернуть код в try/catch и повторять при исключении
  3. Добавить sleep(1) перед сохранением, чтобы запросы разошлись
  4. Хранить баланс в сессии пользователя и сохранять в конце

Разбор

Без блокировки оба запроса читают одинаковый баланс, оба проходят проверку, и списание происходит дважды (lost update). SELECT ... FOR UPDATE внутри транзакции заставит второй запрос ждать, а атомарный условный UPDATE решает задачу одним запросом без явной блокировки. Альтернатива: оптимистичная блокировка через колонку версии. try/catch, паузы и сессия гонку не устраняют.

Ответьте сами, а не прочитайте

В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.