За последние 24 часа нас посетили 22645 программистов и 1209 роботов. Сейчас ищут 753 программиста ...

Некорректная обработка информации, блокировка таблиц.

Тема в разделе "PHP и базы данных", создана пользователем lastdays, 4 окт 2019.

  1. lastdays

    lastdays Активный пользователь

    С нами с:
    27 сен 2012
    Сообщения:
    410
    Симпатии:
    74
    Привет!
    Пробую коротко изложить суть проблемы.
    Если создал тему не в том разделе, прошу простить и перенести.


    Есть приложение работающее на древней конфигурации и выполняющее следующие операции:
    1) Чтение из нескольких таблиц.
    2) Проверка и сравнение данных и обработка информации.
    3) Запись в таблицу базы данных - действий, логов.
    4) Повторное чтение из нескольких таблиц для отображения обновленной информации.

    Также запущен php-демон, который выполняет операции каждые 2 секунды.



    Конфигурация:
    - php 5.4.45 mysqli
    - mysql 5.5.57
    - nginx 1.2.1
    - Тип таблиц InnoDB

    Кусок кода по записи логов:
    PHP:
    1. <?
    2.  
    3. //- $id = айди ( НЕ autoincrement )
    4. //- $log = массив действий
    5. //- Пример: $log = array ( 'time' => date("H:i:s"), 'login' => 'Василий', 'action' => 'Изменение цены товара -1 рубль' );
    6. //-         $log = array ( 'time' => date("H:i:s"), 'login' => 'Анастасия', 'action' => 'Изменение цены товара -1 рубль' );
    7. //-         $log = array ( 'time' => date("H:i:s"), 'login' => 'Demon', 'action' => 'Изменение цены товара -1 рубль' );
    8.  
    9. $sql = array();
    10. //- Перебираем массив действий
    11. foreach ( $log as $l )
    12. {
    13.    $sql[] = array( $id, serialize( $l ) );
    14. }
    15. unset( $l, $log );
    16.  
    17.  
    18. if ( !empty( $sql ) )
    19. {
    20.     //- multi insert
    21.     $db->query("INSERT INTO `logs` ( `id`, `action` ) VALUES ?v ",array( $sql ) );
    22. }
    23. unset( $sql );
    24.  
    25. ?>


    Всё это дело корректно работает, когда операцию выполняет 1 человек и/или demon, но
    когда происходит по 5 записей в секунду от 3 и более пользователей, изредка происходит баг.

    Пример:
    - Цена товара 10 рублей.

    В логах вижу:
    - 23:00:00 Василий изменил цену -1 рубль.
    - 23:00:00 Итоговая цена 9 рублей ( тут все ок ).

    - 23:00:05 Анастасия изменила цену -1 рубль.
    - 23:00:05 Demon изменил цену -1 рубль.
    - Итоговая цена 8 рублей ( а тут словили глюк, два запроса в одно время и лог "влез" в действие чужого пользователя )
    Пример показан с товарами, мне показалось, что так будет легче понять о чем я пишу.

    Ещё раз о проблеме:
    Если 10 разных пользователей выполняют одно и тоже действие, то это действие может происходить в одно и тоже время.
    В связи с чем ловлю баг, когда информация некорректно обрабатывается.


    Вопросы:
    - Какие механизмы можно использовать для блокировки ? Читал немного про lock tables.
    - Нужно учесть, что данные таблицы учавствуют не только в этом модуле, а потому не хотелось бы получить ошибки в других модулях, если таблицы будут заблокированы.
    - Куда копать и рыть туннель? Спасибо!



    P.S. Сам Демон и функционал тестировался длительное время.
    Создавалось условно 100 действий по 6000 возможных операций.
    Каждые 2 секунды демон выбирал 100 раз по 1 из 6000 возможных и ПОДХОДЯЩИХ по условию операций и совершал ее.
    Вся работа скрипта в пределах нормы, около 1 секунды.
    Но при этом стоит заметить, что операции таки достаточно тяжелые.
    На самом деле там выполняются более сложные операции, чем просто изменение стоимости.
     
  2. ADSoft

    ADSoft Старожил

    С нами с:
    12 мар 2007
    Сообщения:
    3.825
    Симпатии:
    738
    Адрес:
    Татарстан
    в куске кода совсем нет вычисления Итоговая цена. Да я думаю оно там и не нужно.
    И в лог не пишите . а итоговую цену на любой момент можно вычислить суммируя все изменения по товара к начальной цене с любым условием. в том числе - на определенную дату/время
     
  3. lastdays

    lastdays Активный пользователь

    С нами с:
    27 сен 2012
    Сообщения:
    410
    Симпатии:
    74

    Смысл в том, что в моем случае могут 50 пользователей выполнить запрос, действия которого изменяют одни и те же данные.
    Загвоздка в том, что при наличии нескольких одновременных запросов происходят глюки, не последовательная запись логов, как пример.

    Как и что сделать, чтобы эти данные корректно и по очереди выполнялись ?
     
  4. kazadai90

    kazadai90 Активный пользователь

    С нами с:
    6 фев 2013
    Сообщения:
    103
    Симпатии:
    19
    используйте очереди, rabbitmq и пр.
     
  5. lastdays

    lastdays Активный пользователь

    С нами с:
    27 сен 2012
    Сообщения:
    410
    Симпатии:
    74
    Транзакции подключены
    по дефолту уровень изоляции
    REPEATABLE-READ

    Как-то влияет на дублирование данных тот факт, что такой же код выполняет отдельный процесс ? я про демона.

    в общем проблему пока так и не решил, даже не знаю, стоит обновлять пых, а вместе с тем и все остальное? Долго черд его дери)
     
  6. lastdays

    lastdays Активный пользователь

    С нами с:
    27 сен 2012
    Сообщения:
    410
    Симпатии:
    74
    Попробовал использовать транзакции. Имеем два случая:
    1) Когда данные изменяЮт пользователи.
    2) Эти же данные изменяет демон (частоту с 2-х сек снизил до 1-й сек )

    Вид сбоку:
    1) SET autocommit=0
    2) START TRANSACTION
    3) много кода.
    много кода.
    много кода (без ошибок конечно же)
    4) COMMIT

    Все равно проскакивают одновременно изменение данных.
    Пробовал всё это дело совместить с блокировкой всех используемых в модуле таблиц, проблему это не исправило, но добавило
    Deadlock found when trying to get lock; try restarting transaction

    Конечно всё обернуть нужно в try и повторить запрос.. только вот самой проблемы это не исправило.
    Т.е. данные как дублировались ( точнее действия ), так и продолжают дублироваться, только теперь с ошибкой ( время от времени ).



    Что-то кажется интересное, но сейчас бы чего по проще, а не переписывать 10к строк кода и вникать в ещё столько же...
     
  7. lastdays

    lastdays Активный пользователь

    С нами с:
    27 сен 2012
    Сообщения:
    410
    Симпатии:
    74
    В общем, кажется проблему решил с помощью транзакций и блокировки строк, а не всей таблицы.
    Несколько критических запросов теперь имеют вид

    SELECT `id` FROM `table` WHERE ? LIMIT 1 FOR UPDATE