Warning: Cannot use a scalar value as an array in /home/admin/public_html/forum/include/fm.class.php on line 757

Warning: Invalid argument supplied for foreach() in /home/admin/public_html/forum/include/fm.class.php on line 770
Форумы портала PHP.SU :: Версия для печати :: Блокировки в MySQL
Форумы портала PHP.SU » » Работа с СУБД » Блокировки в MySQL

Страниц (2): [1] 2 »
 

1. mag1K - 16 Мая, 2012 - 18:40:20 - перейти к сообщению
Есть интернет-магазин, в базе данных есть таблица products(id,name,details,qty).
Есть форма заполнения заявки, где указывается количество товара.
Написал перед уменьшением количества товара и добавления заказа, стандартную проверку на php, проверяющую, что количество требуемого товара, не превышает количество товара на складе. И всё вроде работает.
До тех пор пока пользователи в интернете нажмут на кнопку заказать одновременно( вероятность мала, но есть). Тогда количество товара товара после 1-го заказа ещё не успеет уменьшится, и 2-ой заказ так же пройдёт проверку, и получится что пользователи закажут больше товара чем есть на складе.

Для решения этой проблемы посоветовали использовать блокировки. Вопрос в том какие блокировки использовать?
Как я понимаю всю таблицу блокировать не стоит, достаточно использовать блокировку отдельной записи, с которой работаем.
С помощью какой команды это делается? нашёл SELECT ... FOR UPDATE, но она видимо не совсем подходит.
3. caballero - 16 Мая, 2012 - 19:04:21 - перейти к сообщению
Цитата:
Для решения этой проблемы посоветовали использовать блокировки. Вопрос в том какие блокировки использовать?

глупый совет
никакими блокировками и транзакциями это не делается
и твою проблему не решает никаким каком

единственный способ - резервировать товар (добавть поле в таблицу или специальную таблицу чтобы к юзеру привязать) и возвращать количество без резервированных. В случает незавершения покупки - резевирование отменяется

Цитата:
получится что пользователи закажут больше товара чем есть на складе.

они что заказывают весть товар что видят? или заказывают сколько надо? какое дело юзеру сколько там на складе
проблема на самом деле может быть только если осталось несколько штук. То есть она еще более маловероятна если следить за состоянием склада
4. DeepVarvar - 16 Мая, 2012 - 19:17:14 - перейти к сообщению
caballero пишет:
никакими блокировками и транзакциями это не делается
Я скинул к тому что атомарные операции апдейта и инсерта, как написано, лочат все сами, и тот кто читает из таблицы становится в очередь и ожидает, пока таблица разлочится.

Однако было дело, ставил эксперимент. Создал таблицу на 10 записей (id, name) id не уникальный, не автоинкремент, просто INT.
Добавляя в конец удалял первую запись.
Писатель:
CODE (SQL):
скопировать код в буфер обмена
  1. UPDATE tbl SET id = id -1; INSERT INTO tbl (id, name) VALUES (10, 'что-то там');

Читатель:

Так вот при больших скоростях в цикле или при большом кол-ве "писателей", "читатель" получает не верные данные, дублирование id, больше 10 записей и прочие неинтересности...

Это связано и с кешем мускуля и с тем что в эксперименте у писателя не одна операция, а две.

Лечение: делать все одним запросом.
caballero пишет:
глупый совет
А вот вопрос в принципе верный. Или вы считаете что синхронизация данных не важна?
5. mag1K - 16 Мая, 2012 - 19:37:58 - перейти к сообщению
caballero пишет:
Лечение: делать все одним запросом.


Так даже если сделать всё одним запросом, то они оба сначала пройдут проверку, что товара достаточно, и затем каждый начнёт выполнять свой запрос, при этом один подождёт своей очереди, и всё равно сможет заказать, то чего нет. Разве нет?
вот та самая проверка и работа с таблицами
PHP:
скопировать код в буфер обмена
  1. $query = "SELECT qty FROM `products` WHERE id=$prod_id";
  2.         $qresult=queryMysql($query);
  3.         $res=mysql_fetch_assoc($qresult);
  4.         $ostatok=$res['qty'];
  5.         if($ostatok < $qty){
  6.             die("К сожалению на складе не достаточно товара.<br>");
  7.         } else
  8.           {
  9.             $query="INSERT INTO orders (date,product_id,product_qty,customer_id) VALUES('$date', $prod_id, $qty, $user_id)";
  10.             $qresult=queryMysql($query);
  11.    
  12.             $query="UPDATE products SET qty=qty-$qty WHERE `id`=$prod_id";
  13.             $qresult=queryMysql($query);
  14.           }


где qty-количество товара
(Добавление)
DeepVarvar пишет:
атомарные операции апдейта и инсерта, как написано, лочат все сами, и тот кто читает из таблицы становится в очередь и ожидает, пока таблица разлочится

Даже если тип таблиц у меня InnoDB?
Тоесть если два пользователя одновременно подойдут к строке UPDATE то один из них всё равно будет ждать?
Получается если изменить UPDATE запрос таким образом:
CODE (SQL):
скопировать код в буфер обмена
  1. UPDATE products SET qty=qty-$qty WHERE `id`=$prod_id AND `qty`=$ostatok

То один из них изменит количество, у второго не сработает WHERE т.к. первый изменил поле qty и теперь оно не равно $ostatok. Так?
6. caballero - 16 Мая, 2012 - 20:42:05 - перейти к сообщению
Цитата:
Я скинул к тому что атомарные операции апдейта и инсерта, как написано, лочат все сами, и тот кто читает из таблицы становится в очередь и ожидает, пока таблица разлочится.

не подходит - ему надо залочить пока пока он не поменял значение. А нет изменений - нет транзакций.

Цитата:
А вот вопрос в принципе верный. Или вы считаете что синхронизация данных не важна?

синхронизация там ни при чем

Цитата:
То один из них изменит количество, у второго не сработает WHERE т.к. первый изменил поле qty и теперь оно не равно $ostatok. Так?

здесь нет никаких причин чтобы не сработал update

я вообще не вижу пробьлеммы
у тебя на складе 100 штук
один купил 2 другой 3


какая разница юзеру от чего отнимется эти 3 от 100 или от 98? какое ему вообще дело до того сколько на складе.

единственная проблемма если на складе осталось меньше 5.
7. mag1K - 16 Мая, 2012 - 21:46:17 - перейти к сообщению
caballero пишет:
я вообще не вижу пробьлеммы
у тебя на складе 100 штук
один купил 2 другой 3

проблемы возникнут если один закажет 90 и второй 90.

caballero пишет:
здесь нет никаких причин чтобы не сработал update

до проверка в php, мы вычислим что остаток=100,

потом они одновременно придут к запросу
CODE (SQL):
скопировать код в буфер обмена
  1. UPDATE products SET qty=qty-$qty WHERE `id`=$prod_id AND `qty`=$ostatok

У первого Where, сработает т.к. поле qty=100, а второй дождавшись своей очереди, у него WHERE уже не сработает, т.к. qty=10 после изменений 1-го. а нужно чтобы было 100, разве это не причина чтобы не сработал UPDATE?
8. DeepVarvar - 16 Мая, 2012 - 22:09:55 - перейти к сообщению
mag1K я сейчас не особо вчитывался, но мысли у вас верные.
Есть кстати мысль лочить не базу, а файл с помощью php, там какраз будут ждать все.
Т.е. пока не отработал один с проверками и прочим, другие даже проверять не начнут.
PHP:
скопировать код в буфер обмена
  1. $lockfile = @fopen("./data/lockfile", "w+b");
  2. if ($lockfile) {
  3.  
  4.   // лочим
  5.   flock($lockfile, LOCK_EX);
  6.  
  7.  
  8.   // TODO, делаем все что нужно, никто нам не помешает
  9.   // остальные должны так-же пытаться открывать этот файл
  10.   // и будут вставать в очередь
  11.  
  12.  
  13.   // разлочиваем и закрываем
  14.   flock($lockfile, LOCK_UN);
  15.   fclose($lockfile);
  16.  
  17. }

Данная блокировка файла 100% будет работать под *NIX, нащет винды не скажу...
А там вообще такие вещи есть? caballero, не подскажете?
9. caballero - 16 Мая, 2012 - 23:26:20 - перейти к сообщению
Цитата:
проблемы возникнут если один закажет 90 и второй 90

при таких заказах на складе должно быть несколько тысяч

в любом случае не вижу проблеммы - если товара не хватит он по любому не сможет купить как ни перекладывай цифры в БД.

Цитата:
А там вообще такие вещи есть? caballero, не подскажете?

есть, но я такой фигней не страдаю и не представляю зачем логчить файл
10. DeepVarvar - 17 Мая, 2012 - 09:42:26 - перейти к сообщению
caballero пишет:
при таких заказах на складе должно быть несколько тысяч
Абсолютно согласен - это уже проблемы кладовщика.

Однако, mag1K, если отбросить склад и кладовщика, с программной точки зрения вы правильно мыслите - синхронизация важна.

caballero, хайлоад делали когда-нибудь?
Когда доступ к данным получают одновременно тысячи уников.
Если не делали, вспомните эту тему, когда начнете.
А если уже участвовали в таком проекте - не понятна ваша реакция на вопрос ТС и на ответы людей.
11. Мелкий - 17 Мая, 2012 - 09:58:17 - перейти к сообщению
Зачем вам блокировки, если всё, что вам нужно - не продать больше, чем есть?
CODE (SQL):
скопировать код в буфер обмена
  1. UPDATE products SET qty=qty-$qty WHERE `id`=$prod_id AND qty>=$qtu;

Если affected_rows > 0 - пишете в orders, товар успешно зарезервирован.
Если 0 - на складе недостаточно шушпанчиков.

Если дальше есть ещё условия, которые могут прервать заказ - оборачиваете всё в транзакцию и делаете rollback при сбое.

mag1K пишет:
Даже если тип таблиц у меня InnoDB?
Тоесть если два пользователя одновременно подойдут к строке UPDATE то один из них всё равно будет ждать?

Блокировка у innoDB построчная. Т.е. если 2 транзакции попытаются изменить 1 строку, вторая транзакция будет ждать окончания первой.
12. DeepVarvar - 17 Мая, 2012 - 09:59:33 - перейти к сообщению
Мелкий пишет:
оборачиваете всё в транзакцию
В одну Закатив глазки
13. Мелкий - 17 Мая, 2012 - 10:47:42 - перейти к сообщению
Отдельно замечу, что ключевое слово "транзакция" (в контексте begin, commit, rollback, а не атомарной операции СУБД) здесь не главное и проблему первоначального кода не решит, а только запутает.
Дело в том, что update будет ждать окончания транзакции. А select-то - нет.
Первая транзакция сделает селект, получит, например, 1, потом заапдейтит -1, должно остаться 0. Но если до момента commit'а первой стартует вторая транзакция - селект вернёт опять же 1. И после апдейта (вот который таки начнёт ждать окончание первой транзакции) и коммита второй транзакции значение в таблице будет отрицательным.

А решением коллизии в этом случае окажется уже всплывший в начале SELECT ... FOR UPDATE, который залочит строку на время этой транзакции. А если лок не прошёл - будет ждать, пока не сможет его захватить.
14. caballero - 17 Мая, 2012 - 10:48:00 - перейти к сообщению
Цитата:
А если уже участвовали в таком проекте - не понятна ваша реакция на вопрос ТС и на ответы людей.


потому что вопрос бессмысленный и соответственно ответы на него тоже.
Еще раз - если человек просто выбрал количество товаров но не изменил ничего в БД никакие транзакции или блокировки работат не будут.
Нет изменений - нет проблем с консистентностью (это то что ты называешь почему то синхронизацией) данных.

Если резервировать товар (в смысле количество товара в той же строке) то транзакции все равно не подходят - длинные транзакции (пока там юзер сформирует заказ) от нескольких юзеров заблокируют БД или еще хуже - приведут к дедлоку. Да и вряд ли получится транзакцию растянуть на несколько обращений к серверу - конект то каждый раз новый.

Еще раз - если товара достаточно ничего делать не нужно вообще. Если нет - все равно заказ выполнен не будет. Проблемма высосана их пальца. Тем более это интернет - у юзера скорее заглючит браузер или связь чем он наткнется на маловероятную ситуацию что кто то в тот же момент захочет купить такое количество что суммарное количество превысит то что на складе.
15. DeepVarvar - 17 Мая, 2012 - 11:26:56 - перейти к сообщению
caballero пишет:
Да и вряд ли получится транзакцию растянуть на несколько обращений к серверу - конект то каждый раз новый
На примере файла - коннектов/запросов/транзакций можно делать несколько, и лочить файл не на весь процесс формирования заказа, а только на момент апдейта данных в БД - это доли секунды.

 

Powered by ExBB FM 1.0 RC1