Всём привет. Есть сайт на котором хранятся виртуальные финансы для сайта и система зачисления, вывода, перевода между пользователями и т. д. Ближе к теме, один человек смог каким-то образом вывести сумму два раза одну и ту же, все это дело произошло в один момент, дата двух транзакций одинаковая, сумма первой транзакции полная стоимость его финансов на сайте, а вторая получается транзакция которая создавалась она не должна была создаться так как сумма на счету 0. Все защиты от F5 и тому подобное присутствуют. Вопрос: как это сделали?
Само решение я так понимаю кроется в mysql транзакциях и как этим пользоваться я примерно понимаю меня больше интересует как такое можно сделать чтоб бы больше понимать.
Вот кусок кода который отвечает за создание заявки:
$paySys = Paymsys::findByName($this->tasys_in, $this->amount_in)->setPersent(true);
if ($paySys->sysAbbr == null) {
Yii::$app->session->setFlash('error', "<div class=\"alert alert-danger text-center\">Направильное имя системы</div>");
return false;
}
$this->amount_in_origin = $paySys->sum;
$this->tasys_in = $paySys->sys;
$this->user_real_sum = UserMoney::getSum($paySys->sysAbbr);
$money = OperationsMoney::start($paySys)->moneyDeduct();
if (!$money->save()) {
Yii::$app->session->setFlash('alert', $money->getError() . " " . $money->getSum() . ' ' . $paySys->sysAbbr);
return false;
}
Дата хранится в обычном формате 2016-08-22 00:21:51
Рассмотрим два параллельных потока (1) и (2)
Способ решения: на 4 этапе блокировать таблицу. Тогда получим такую картину:
В коде это будет выглядеть так
mysqli_autocommit(false);
$res = mysqli_query("SELECT money FROM my_table WHERE user_id = $id LOCK FOR UPDATE");
$row = mysqli_fetch_row($res);
mysqli_free_result($res)
if ($row[0] >= $val)
mysqli_query("UPDATE my_table SET money = GREATEST(money - $val, 0) WHERE user_id = $id");
else
echo "Облом!";
mysqli_commit();
Параметры и обработку ошибок добавить по вкусу
дата двух транзакций одинаковая
На сколько точная дата? mysql
для timestamp
и datetime
хранит как самую малую единицу всего лишь секунду. Одна секунда - это очень много времени. За такое время даже человек может умудриться сделать несколько запросов. А автоматика с лёгкостью.
Классический race condition
в вашем коде. Надо внимательно смотреть код и думать, что может пойти не так при конкурентном доступе.
СУБД вполне неплохое место для сериализации транзакции. Но про конкурентный доступ надо будет всё равно думать. Просто сказать begin/commit
недостаточно.
Все защиты от F5 и тому подобное присутствуют
Очевидный вывод - эти проверки были реализованы некорректно или только для частных случаев.
Без кода сказать ничего нельзя, кроме как "race condition". С кодом - скорей всего его довольно много и просто так вычитывать желающих может не найтись.
Внедрение транзакций в готовый код может оказаться очень сложным делом. Если у вас немного таких критических мест, то можно использовать семафоры http://php.net/manual/ru/ref.sem.php Или организовать подобное на memcache http://php.net/manual/ru/memcached.add.php
Виртуальный выделенный сервер (VDS) становится отличным выбором
Всем доброго времени суток, недавно начал работать с webpackНастроил его для раработы и все вроде бы хорошо, но вот мне понадобилось изменить...
Нашел функцию преобразующую заголовки на русском и казахском языках в английские буквы
Ситуация такая Apple Pay и tinkoff После создания платежной сессии
Подскажите какой можно написать тест PhpUnit на следующий метод(метод парсит ресурс и сохраняет ссылки в csv)