Два покупателя списали последний товар: условие внутри UPDATE
В приложении есть товар с остатком один. Каждый запрос сначала читает остаток, проверяет его в Python и затем записывает новое значение. Два параллельных запроса успевают прочитать единицу. Каждый считает покупку разрешённой, хотя товар нужен обоим.
Проверка в приложении и изменение строки выполняются в разные моменты. Между ними другой запрос может изменить остаток. Для простого списания проверку можно перенести в сам UPDATE: изменение допускается, только пока актуальное значение удовлетворяет условию.
Как изменить решение
UPDATE stock
SET available = available - 1
WHERE product_id = 42 AND available >= 1
RETURNING product_id, available;Первый успешный запрос вернёт строку с новым остатком. Второй после ожидания конкурентного изменения не найдёт подходящую строку при Read Committed и вернёт пустой результат. Приложение должно трактовать отсутствие RETURNING как отказ списания, а не как успешную покупку.
Заказ и списание нужно зафиксировать одной транзакцией. Если создание заказа упало, откат должен вернуть остаток. Для заказа с несколькими товарами договоритесь о порядке блокировки строк: например, всегда по product_id. Это уменьшает вероятность взаимного ожидания при пересекающихся корзинах.
Что проверить на своих данных
Проверьте двумя соединениями: начните транзакцию в первом, выполните списание и не фиксируйте её; во втором выполните такую же команду. После COMMIT первого второе соединение должно получить ноль строк. Затем повторите опыт с ROLLBACK: второе списание сможет пройти.
Добавьте CHECK (available >= 0) как защиту целостности и NOT NULL для остатка. Ограничение не заменяет обработку отказа: оно помогает обнаружить другие пути записи, которые забыли условие. Для запроса нескольких единиц используйте available >= :quantity и проверяйте положительность quantity на входе. Отрицательное количество иначе превратит списание в пополнение.
Практика дальше
Списание становится полезным, когда обработчик API корректно возвращает отказ и сохраняет заказ. В курсе FastAPI вы собираете backend с SQLAlchemy, CRUD и тестами pytest. На последовательном проекте можно закрепить управление сессией и проверку ответов; база практики курса — SQLite.
Посмотреть программу и практику курса
Источник: Два покупателя списали последний товар — официальная документация.
Другие задачи по теме: PostgreSQL: запись, блокировки и планы запросов.
Продолжить на практике
- FastAPI для начинающих: API с базой данных и тестами ↗
Соберёте свой первый полноценный backend на FastAPI: с базой данных, регистрацией, JWT-авторизацией и автотестами. 34 коротких урока и 140 заданий с проверкой — бесплатно.
