@MeatPixel

Что я не так делаю с транзакциями PHP MYSQL?

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

Собственно суть вопроса, у меня есть метод add_balance, он в свою очередь принимает аргумент, сумму для пополнения баланса пользователя, я беру из базы последнюю актуальную запись баланса и хочу к этому балансу добавить новую сумму.

Собственно как решить проблему одновременных транзакций (обращений в базу)?
Тестировал разными способами, в конечном итоге все равно баланс в базе данных сбивается после одновременных обращений к базе. Попросту в момент второго одновременного запроса, баланс уже изменен, и второй запрос работает по сути с предпоследней строкой, а не с последней, то есть уже с неактуальной.

Собственно как быть? Вот мой код, упростил и убрал все лишнее для вашего удобства.

public function add_balance($add_balance){

    $transaction = ORM::get_db()->beginTransaction();

    if($transaction){

        //Получаю объект с вктуальным балансом
        $LAST_TR = ORM::forTable('transactions')->where('user_id', UID)->orderByDesc('time')->findOne();

        $last_balance = $LAST_TR->balance;

        $TR = ORM::forTable('transactions')->create();
        $TR->user_id = UID;

        $TR->setExpr('time', 'NOW()');
        $TR->balance = $last_balance + $add_balance;
        $TR->new_balance = $add_balance;
        $TR->old_balance = $last_balance;

        $TR->save();

        ORM::get_db()->commit();

    }else{
        ORM::get_db()->rollBack();
        echo 'ОШИБКА: Уже выполняется другая транзакция';
    }

}


PS.
Использую библиотеку idiOrm для работы с базой данных, в данной ОРМ нет своих методов для работы с транзакциями, есть только возможность вызова стандартных методов PDO
ORM::get_db()->beginTransaction();  // Равносильно PDO::beginTransaction();
ORM::get_db()->commit(); // Равносильно PDO::commit();
ORM::get_db()->rollBack(); // Равносильно PDO::rollBack();
  • Вопрос задан
  • 457 просмотров
Пригласить эксперта
Ответы на вопрос 3
nokimaro
@nokimaro
Меня невозможно остановить, если я смогу начать.
Вам нужны не просто транзакции, так как они гарантируют атомарность, а блокировки (LOCKING) чтобы гарантировать что до тех пор пока не завершится ваша транзакция другой процесс не сможет получить доступ к строке
SELECT ... FOR UPDATE https://dev.mysql.com/doc/refman/8.0/en/innodb-loc...
Ответ написан
FanatPHP
@FanatPHP
Чебуратор тега РНР
В общем в качестве ответа на этот малопонятный вопрос можно выдать только общие рекомендации

Во-первых, транзакции в БД не имеют вообще никакого отношения к транзакциям в бухгалтерии.
И к увеличениям балансов.
Транзакция в БД несет очень простую функцию: гарантировать что все запросы внутри транзакции завершились успешно. то есть чтобы не было неконсистентных данных - в одну строку добавились данные, а во вторую нет. Поэтому транзакция в случае ошибки откатывает все изщменения, которые ты успел сделать.

При этом все поголовно новички путают транзакции с блокировками. Которые собственно и делают то что тебе хочется - блокируют чтение или запись в таблицу.

Но при этом тебе и блокировки тоже не нужны, поскольку заниматься такой ерундой, как "прочитать, добавить и записать" в принципе не нужно, поскольку можно совершить атомарную операцию - СРАЗУ в запросе и прибавить нужную сумму, без придварительного чтения.

Но самое главное - тебе сначала надо решить, каким образом вообще ведется бухгалтерия - добавлением строк, или обновлением строк.
Ответ написан
@Frantick
Программист
Если вы в начале в переменную складируете транзакцию, то почему разрешаете или отклоняете новую сущность опять? Используйте $transaction->commit(); ...
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы