За последние 24 часа нас посетил 21881 программист и 1150 роботов. Сейчас ищет 341 программист ...

Защита от дублированных операций

Тема в разделе "MySQL", создана пользователем IvanKut, 23 июн 2020.

  1. IvanKut

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

    С нами с:
    27 июл 2018
    Сообщения:
    258
    Симпатии:
    0
    Приветствую
    У меня есть скрипт, который из одной таблицы берет select SUM(amount) as n from billing
    И если сумма положительная вставляет задачу на выплату с полученной суммой и обновляет факт выплаты в таблицы billing

    Но возник вопрос - а предположим, получиться так, что случайно 2 пользователя одновременно нажмут кнопку выплаты, и запрос на select SUM() для двоих произойдут одновременно, до того, как у первгого случится обновление факта выплаты в таблице billing.

    Как правильно от таких ситуаций защишаться?
    Благодарю!
     
  2. ADSoft

    ADSoft Старожил

    С нами с:
    12 мар 2007
    Сообщения:
    3.823
    Симпатии:
    736
    Адрес:
    Татарстан
    Очень крайне маловероятно, хоть пару микросекунд да будет разница. А вообще такие вещи в транзакции оборачиваеются..
    Ни странно как то все всегда всю сумму могут вывести? Обычно у каждого пользователя свой счёт и выплаты с него идут
     
  3. IvanKut

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

    С нами с:
    27 июл 2018
    Сообщения:
    258
    Симпатии:
    0
    @ADSoft есть таблица billing - где идут начисления. Когда менеджер проводит выплату, каждому начислению ставиться флаг что выплачено.
    Так вот между вытаскиванием строк, которые доступны для выплаты И проставлением флага доли микросекунл. И получается, может случиться так, что второй запрос на вытаскивание доступных к выплате строк, пройдет быстрей, чем проставятся по 1 запросу метки выплачено.

    Нашел хорошую статью
    https://habr.com/ru/post/238119/
    SELECT * FROM requests WHERE id=5 FOR UPDATE
    Автоматом для полученных строк сделает LOCK

    Вопрос, правильный алгоритм такой(уточняю?)
    1 - set autocommit=0; //отключаем autocommit
    2 - start transaction; (также, можно написать BEGIN; )
    3 - Выполняем запросы
    SELECT * FROM requests WHERE id=5 FOR UPDATE (НАДО ЛИ ДОБАВЛЯТЬ ЭТО?)
    UPDATE
    4 - commit; //Фиксация действий, запись их в физическую БД

    Но еще наткнулся на https://habr.com/ru/post/46542/
    Впринципе, блокировки решают задачу, которую я хочу решить. Не понятно, в каком случае лучше использовать транзакции а в какой блокировки?
     
  4. IvanKut

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

    С нами с:
    27 июл 2018
    Сообщения:
    258
    Симпатии:
    0
    Попробовал использовать транзакции.
    Вопрос - а если ли mysql команда - commit IF no error - типа закомитеть, только если в транзакции не было ошибок?
    Либо нужно каждый запрос проверять если НЕ ОК делать rollback?
    [​IMG]
     
  5. ADSoft

    ADSoft Старожил

    С нами с:
    12 мар 2007
    Сообщения:
    3.823
    Симпатии:
    736
    Адрес:
    Татарстан
    Не надо принтскринов, оформляйте текст соотв тегами
     
  6. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.072
    Симпатии:
    1.237
    Адрес:
    там-сям
    В базах данных есть такое понятие "уровень изоляции". Для MySQL по умолчанию действует уровень REPEATABLE READ.
    Это значит, что пока длится твоя транзакция, ты будешь видеть данные такими, какими они были в начале транзакции, даже если что-то изменилось.
    Как это может отразиться на твоих "параллельных" расчетах выплат? Давай условно будем называть сеансы расчёта как "первый" и "второй", потому что всегда кто-то начинает раньше.

    - если первый записал данные раньше чем стартовал второй, то нет проблемы, очевидно.

    - если второй начал читать данные до того, как закончил работу первый, то второй увидит исходные данные и может повторить тот же расчёт, что и первый. второй должен прийти к тем же цифрам что и первый, так? проблема может возникнуть только если он создаст ещё одну запись с итогами, а не перезапишет существующую.

    Это можно разрулить с помощью уникальных индексов. Надо создать условия в которых попытка записать расчёт за тот же период для того же подразделения/работника и т.д. должна заканчиваться ошибкой! БД может отказать тебе, если нарушается уникальность. Придумай какое сочетание полей в твоём billing должно быть ункальным и неповторимым и создай индекс. Например пара (ид_работника, расчетный_месяц).

    Гуглить:
    MySQL ISOLATION LEVEL
    MySQL ADD UNIQUE INDEX
    MySQL INSERT IGNORE
    MySQL INSERT ON DUPLICATE KEY UPDATE
    MySQL REPLACE
    --- Добавлено ---
    P.S. Дополнительно можно использовать блокировку в начале расчёта, чтобы второй не смог его начать, если первый не закончил.

    Гуглить:
    MySQL NAMED LOCKS