我的应用程序是用php/laravel和MySQL数据库实现的。在这个系统中,用户可以将钱存入钱包。我在users表中有一个wallet_balance列。当用户将资金存入他们的钱包时,我将该列更新如下:
public function topup(Request $request)
{
$user = auth()->user();
$deposit_amount = $request->deposit_amount;
//e.g.
//$deposit_amount = 100
//$user->wallet_balance = 50
$user->wallet_balance = $user->wallet_balance + $deposit_amount;
$user->save();
// After saving I expect $user->wallet_balance to be 150 which works perfectly.
}有些服务从用户的钱包中扣除费用(在另一个功能中)。例如:
public function chargeService(Request $request)
{
$user = User::findOrFail($request->user_id);
$service_fee = $request->service_fee;
//$service_fee = 30
//$user->wallet_balance = 150
$user->wallet_balance = $user->wallet_balance - $service_fee;
$user->save();
// After saving I expect $user->wallet_balance to be 120 which works perfectly.
}完成两次交易后,用户的钱包余额应该是120。但是,在极少数情况下,这两个事务可能会同时发生。这意味着在存款交易中,初始钱包余额是50,对于服务费交易,初始钱包余额是50 (因为他们在两者中的任何一个更新钱包余额之前同时查询数据库)。危险就在这里。第一个事务的钱包余额为150 (50 + 100),第二个事务的钱包余额为20 (50 - 30)。这使得用户的钱包余额为150或20,这取决于哪个操作最后更新用户的钱包余额。
现在,我的问题是,我如何才能接近我的钱包系统,以避免这个臭名昭著的问题。我会很感谢你们的。
你可以建议一种更好的方式来表达这个问题。
发布于 2021-05-19 20:54:17
您面临的主要问题是PHP中加载的用户状态不能反映数据库中的内容。您应该对数据库执行查询:
$params = ['fee' => 30, 'user_id' => $request->user_id];
DB::statement('UPDATE users SET wallet_balance=wallet_balance-:fee WHERE id=:user_id', $params);或锁定记录进行更新:
try {
DB::beginTransaction();
// it is important to get the state from the
// database at the time of locking
$user = User::query()->lockForUpdate()->findOrFail($request->user_id);
$user->wallet_balance = $user->wallet_balance - $service_fee;
$user->save();
DB::commit();
} catch (Throwable $e) {
DB::rollBack();
}发布于 2021-05-19 19:08:42
你必须使用原始的mysql查询来锁定表格的行,我不知道eloquent是否有针对这种类型的查询的一些功能,但你可以深入挖掘。看一看这篇文章,对https://medium.com/@ibraheemabukaff/locking-rows-in-mysql-e84fd3bbb8cd有一个清晰的了解
https://stackoverflow.com/questions/67598992
复制相似问题