PHP Mysql - 创建自己的银行系统
PHP Mysql - creating own banking system
我正在开发一个项目,用户可以用真钱购买 "coins"。他们还将能够在交易系统中以真钱出售这些代币。他们甚至可以将硬币发送给其他用户。这将写在 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));
谢谢
布莱恩
您需要将获取旧余额的查询和设置新余额的查询放在同一个事务中。所以你需要把 $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));
您不需要为此显式创建事务,因为语句始终是它自己的事务。
我正在开发一个项目,用户可以用真钱购买 "coins"。他们还将能够在交易系统中以真钱出售这些代币。他们甚至可以将硬币发送给其他用户。这将写在 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));
谢谢 布莱恩
您需要将获取旧余额的查询和设置新余额的查询放在同一个事务中。所以你需要把 $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));
您不需要为此显式创建事务,因为语句始终是它自己的事务。