Проблема конкурентного доступа к остаткам на складе и её решение
Рассмотрим типичную ситуацию на складе. Существует таблица с данными о товарах, содержащая, как минимум, два поля: ID товара и количество на остатке. При сканировании каждой единицы товара система выполняет последовательность действий:
- Запрашивает из базы данных текущий остаток для конкретного товара.
- Уменьшает полученное значение на единицу.
- Сохраняет обновлённый остаток обратно в базу.
На практике между получением остатка и его сохранением может происходить множество дополнительных операций: запросы к другим таблицам, сложные вычисления, валидация. Это создаёт временной промежуток, в течение которого состояние данных может измениться.
Суть проблемы
Если несколько операторов одновременно сканируют один и тот же товар, возникает классическая проблема конкурентного доступа (race condition). Два параллельных процесса могут получить из базы одинаковое значение остатка (например, 100). Каждый из них вычтет свою единицу (получив 99) и попытается записать это значение обратно. В результате, несмотря на два факта списания, остаток уменьшится только на 1, что приведёт к нарушению целостности данных.
Возможные подходы к решению
Для предотвращения таких ошибок существует несколько технических решений:
- Атомарная операция UPDATE: Самый простой и эффективный способ - выполнять изменение остатка одним запросом, без предварительного SELECT. Например:
UPDATE warehouse SET balance = balance - 1 WHERE product_id = 5. Этот запрос выполняется на стороне СУБД атомарно, исключая конкуренцию. - Блокировки на уровне строк или таблиц: Использование механизмов транзакций с подходящим уровнем изоляции (например, REPEATABLE READ или SERIALIZABLE) или явных блокировок (SELECT ... FOR UPDATE) позволяет «зарезервировать» строку для изменения одним процессом до завершения транзакции.
- Версионирование (оптимистичная блокировка): Добавление в таблицу поля-версии (timestamp или counter). Перед обновлением проверяется, не изменилась ли версия записи с момента её чтения.
- Очередь операций: Все запросы на изменение склада ставятся в очередь и обрабатываются последовательно.
Выводы и рекомендации
Для задачи простого счётчика наиболее рациональным решением является атомарный UPDATE. Он гарантирует корректность данных, максимально производителен и не требует сложной логики на уровне приложения.
Важно отметить, что для учётных систем, требующих полного аудита всех операций, ведение остатков только через прямое обновление поля может быть недостаточно. В таких случаях рекомендуется дополнительно вести отдельную таблицу транзакций (движений), где каждая операция списания или прихода фиксируется отдельной записью. Итоговый остаток тогда можно рассчитать как сумму всех движений, что обеспечивает полную историчность и контроль.