Как избежать ошибки лишней траты в онлайн магазине?
Код работает на PHP.
База данных: MySQL.
Есть онлайн магазин.
По клику на "Купить" число товара уменьшается на 1.
Если поставить кликер и очень быстро слать запросы на сервер, то несмотря на то, что в коде есть проверка наличия товара проходит ошибка, в результате, который в корзину можно добавить больше товара, чем есть на самом деле.
Такое впечатление, что запрос, одновременно выполняющийся с другим, проходит проверку наличия товара до того, как предыдущий запрос успеет это количество изменить.
Как решить данную проблему?
Из-за чего данная проблема возникает? (собственно свою догадку я высказал)
Вы точно обсуждали этот момент с заказчиком ? Обычно просят
1) не показывать точное кол-во на складе : один / мало / много
2) кол-во на складе меняется после действий менеджера
а) подтверждения заказа
б) оплаты клиентом
Если хочешь интерактива, то выводи кол-во на складе - минус в его корзине.
при добавлении в корзину одного и того же товара, нужно увеличивать кол-во у существующей записи,
а не добавлять новую строчку, тогда проверка становиться простой,
В случае больше чем на складе присвоить значение доступного кол-ва.
Но таким образом вскрывается реальное состояние склада, а заказчики, такого обычно не любят
Правильный ответ дал Vitsliputsli.
Все прочие проверки в коде у меня есть, блокировка форм пока не пришел ответ сервера, конечно, тоже. Нужно именно блокировать строки таблицы.
А пример, кода? Или хотя бы функций/запросов, которые нужно использовать, чтобы было от чего копать?
Пока придумал вариант с lock table. Но вариант выглядит так себе, ведь тогда работать будет медленнее - пока один пользователь работает к таблицей, все остальные ждут.
time_is_always_against_us, прочитайте про SELECT FOR UPDATE. И блочьте не всю таблицу, а только строку/столбец или лучше ячейку.
Будет медленнее, если много запросов на чтение не связанных с изменениями, тут нужно знать вашу ситуацию. Если будет напрягать, то можно разделить на таблицу с реальными данными, и на витрину (мат.вьюху), где будут данные с более низкой точностью, но доступные без блокировок.
Vitsliputsli, Я правильно понял?
1. LOCK IN SHATE MODE - блокирует строку на запить, но разрешает чтение.
Нужно применять, когда запросы update к строке может посылать только 1 пользователь (например, владелец аккаунта), а другие могут просто просматривать его профиль (не влияя на данные).
2. FOR UPDATE - блокирует и чтение, и запись строки.
Нужно применять, когда запросы update к строке может посылать несколько пользователей (мой случай с количеством товара).
Пример кода:
$result=mysqli_query($mysqli_connection, "SELECT * FROM shop WHERE id=2 FOR UPDATE");
$data=mysqli_fetch_assoc($result);
/* Что-нибудь делаем с данными... */
mysqli_query($mysqli_connection, "UPDATE shop SET amount={$data['amount']} WHERE id=2");
time_is_always_against_us, SHARE MODE никогда не использовал. Насколько знаю, это больше для сохранения целостности БД, если мы добавляем зависимые данные, а в это время данные от которых зависим могут быть удалены. Обычно, я решал это, либо не в СУБД, либо процедурой с exception. То, что вы описываете, обычно не решают на стороне СУБД. Мнения разнятся, но большинство считает, что логика должна лежать в одном месте, и как правило, это не СУБД.
Проверьте FOR UPDATE, там должны быть дополнительные параметры блокировки, вам в идеале нужно блочить только ячейку. При такой блокировке, другой SELECT легко пройдет, если в нем нет работы с этой ячейкой. Ну и, все засовывайте в одну транзакцию, блокировки снимаются и по окончанию транзакции тоже.
Главное, чтобы ему (боту) не удалось оформить товаров больше, чем есть на самом деле.
При переходе в оформление заказа всё пересчитать заново, если товаров набрано больше, чем есть, вывести ошибку или присвоить максимально допустимое кол-во.
У вас с дизайном беда. Нельзя изменять количество доступного для покупки товара ДО оформления заказа. Иначе вам достаточно быстро скликают все стоки. От того, что пользователь положил товар в корзину, на складе его меньше не становится.
А уже в момент оформения заказа обновляйте стоки. И следите за тем, чтобы в корзину не положили товара больше, чем реально есть на складе.
1) блокировать кнопку на время выполнения скрипта
2) валидировать данные на сервере, и если значение больше чем нужно, то отображать ошибку, или приравнивать к максимальному значению.
1) Да, так и сделал, но это не панацея. Все это только у пользователя в браузере и подделать данные элементарно. 99,9% так делать не будет, но может найтись какой-нибудь умник.
2) Валидация на сервере есть: "в коде есть проверка наличия товара".