PHP Mysql — создание собственной банковской системы

Я работаю над проектом, где пользователи будут покупать «монеты» за реальные деньги. Они также смогут продавать эти монеты за реальные деньги в транзакционной системе. Они могут даже иметь возможность отправлять монеты другим пользователям. Это будет написано на PHP. Я понимаю транзакции mysql, но я не уверен на 100%, что это должно быть на 100% из ошибок параллелизма. Я создал псевдокод, в котором, как мне кажется, есть недостаток.

    function withdraw($id, $amount_to_withdraw) {
$ret = false;
$balance = $db->getBalanceById($id);

$this->makeSureAmountIsNotNegative($amount_to_withdraw);

$new_balance = $balance - $amount_to_withdraw;

if ($new_balance >= 0.00) {
try {
$db->startTransaction();

// THIS IS WHERE THE FLAW IS!!!
$db->do("UPDATE account SET balance = ? WHERE id = ?", array($new_balance, $id));

$db->commit();

$ret = true;
} catch (Exception $e) {
$db->rollback();
}
}

return $ret;
}

Насколько я понимаю, в PHP возможно выполнение еще одного запроса до того, как он будет полностью завершен, и он может установить баланс ниже нуля и другие ошибки, связанные с параллельным выполнением.
Как мне написать этот код, чтобы он был безопасен от этих ошибок.
Нужно ли блокировать MySQL на уровне строк, как:

// ROW LEVEL LOCKING FOR UPDATE
$balance = $db->select("SELECT balance FROM account WHERE id = ? FOR UPDATE;", array($id));

Спасибо
Брайан

0

Решение

Вам нужно поместить запрос, который получает старый баланс, и тот, который устанавливает новый баланс, в ту же транзакцию. Так что нужно поставить $db->startTransaction() до $db->getBalance($id);и положить все это внутри try блок.

function withdraw($id, $amount_to_withdraw) {
$ret = false;

try {
$db->startTransaction();

$balance = $db->getBalanceById($id);

$this->makeSureAmountIsNotNegative($amount_to_withdraw);

$new_balance = $balance - $amount_to_withdraw;

if ($new_balance >= 0.00) {
$db->do("UPDATE account SET balance = ? WHERE id = ?", array($new_balance, $id));

$ret = true;
}
$db->commit();

} catch (Exception $e) {
$db->rollback();
}

return $ret;
}

Но более простым способом является вычитание в UPDATE запрос, а не делать два запроса.

$db->do("UPDATE account SET balance = balance - ?
WHERE id = ? AND balance >= ?", array($amount_to_withdraw, $id, $amount_to_withdraw));

Вам не нужно явно создавать транзакцию для этого, поскольку оператор всегда является собственной транзакцией.

1

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]