Представьте, что вы работаете над системой электронной коммерции, и тысячи людей одновременно пытаются купить последний оставшийся продукт. Однако многие из них смогли перейти к кассе и завершить заказ. Когда вы проверяете свои запасы, у вас есть продукт с отрицательным количеством. Как это стало возможным и как вы можете это решить?
Давайте кодировать! Первое, о чем вы можете подумать, — это проверить наличие товара перед оформлением заказа. Может быть, что-то вроде этого:
public void validateAndDecreaseSolution(long productId, int quantity { OptionalstockByProductId = stockRepository.findStockByProductId(productId); int stock = stockByProductId.orElseThrow().getStock(); int possibleStock = stock - quantity; if (stock Вы можете использовать эту проверку, но когда мы говорим о сотнях, тысячах, миллионах или даже десятках запросов в секунду, этой проверки будет недостаточно. Когда 10 запросов достигнут этого фрагмента кода одновременно и база данных вернет одно и то же значение для stockByProductId, ваш код сломается. Вам нужен способ заблокировать другие запросы, пока мы проводим эту проверку.
Первое решение - ДЛЯ ОБНОВЛЕНИЯ
Добавьте оператор блокировки в свой SELECT. В этом примере я сделал это, используя FOR UPDATE с Spring Data. Как сказано в документации PostgreSQL
FOR UPDATE приводит к тому, что строки, полученные инструкцией SELECT, блокируются, как если бы они были обновлены. Это предотвращает их изменение или удаление другими транзакциями до тех пор, пока текущая транзакция не завершится.
@Query(value = "SELECT * FROM stocks s WHERE s.product_id = ?1 FOR UPDATE", nativeQuery = true) OptionalfindStockByProductIdWithLock(Long productId); public void validateAndDecreaseSolution1(long productId, int quantity) { OptionalstockByProductId = stockRepository.findStockByProductIdWithLock(productId); // ... validate stockRepository.decreaseStock(productId, quantity); } Все запросы к таблице акций с использованием идентификатора продукта будут ждать завершения фактической транзакции. Цель здесь — убедиться, что вы получаете последнюю обновленную стоимость акций.
Второе решение — pg_advisory_xact_lock
Это решение похоже на предыдущее, но вы можете выбрать ключ блокировки. Мы заблокируем всю транзакцию до тех пор, пока не завершим всю обработку проверки и уменьшения запасов.
public void acquireLockAndDecreaseSolution2(long productId, int quantity) { Query nativeQuery = entityManager.createNativeQuery("select pg_advisory_xact_lock(:lockId)"); nativeQuery.setParameter("lockId", productId); nativeQuery.getSingleResult(); OptionalstockByProductId = stockRepository.findStockByProductId(productId); // check stock and throws exception if it is necessary stockRepository.decreaseStock(productId, quantity); } Следующий запрос будет взаимодействовать только с продуктом с тем же идентификатором после завершения этой транзакции.
Третье решение - предложение WHERE
В этом случае мы не будем блокировать нашу строку или транзакцию. Давайте разрешим этой транзакции продолжаться до оператора обновления. Обратите внимание на последнее условие: запас > 0. Это не позволит нашему запасу быть меньше нуля. Таким образом, если два человека попытаются купить одновременно, один из них получит сообщение об ошибке, поскольку наша база данных не допускает акции
@Transactional @Modifying @Query(nativeQuery = true, value = "UPDATE stocks SET stock = stock - :quantity WHERE product_id = :productId AND stock > 0") int decreaseStockWhereQuantityGreaterThanZero(@Param("productId") Long productId, @Param("quantity") int quantity);Заключение
Первое и второе решения используют в качестве стратегии пессимистическую блокировку. Третий – оптимистическая блокировка. Стратегия пессимистической блокировки используется, когда вы хотите ограничить доступ к ресурсу при выполнении любой задачи, связанной с этим ресурсом. Целевой ресурс будет заблокирован для любого другого доступа, пока вы не завершите процесс. Будьте осторожны с взаимоблокировками!
При оптимистической блокировке вы можете выполнять различные запросы к одному и тому же ресурсу без какой-либо блокировки. Он используется, когда конфликты маловероятны. Обычно у вас есть версия, связанная с вашей строкой, и когда вы обновляете эту строку, база данных сравнивает версию вашей строки с версией строки в базе данных. Если оба равны, изменение будет успешным. Если нет, вам придется повторить попытку. Как видите, в этой статье я не использую ни одной строки версии, но мое третье решение не блокирует никакие запросы и контролирует параллелизм, используя условие stock > 0.
Если вы хотите увидеть полный код, вы можете проверить его на моем GitHub.
Существует много других стратегий для реализации пессимистической и оптимистичной блокировки, например, вы можете поискать больше о FOR UPDATE With SKIP LOCKED.
Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.
Copyright© 2022 湘ICP备2022001581号-3