情景:(代码环境Thinkphp)
我有一个流量充值接口,里面的操作大概是这样:
1.生产一个订单
2.查询用户余额是否足够,足够继续,不足提示错误
3.扣除用户余额,并生产账户记录
4.将订单设为已充值
$model
->startTrans();
// 启动事务
try
{
$uInfo
= D(
'User'
)->where(
array
(
'id'
=>
$uInfo
[
'id'
]))->find();
// 判断用户余额是否足够
if
(
$uInfo
[
'balance'
] <= 0 ||
$uInfo
[
'balance'
] <
$orderData
[
'total_price'
] ) {
throw
new
\Think\Exception(
"用户余额不足"
,5202);
}
// 扣除用户余额,并生产账户记录
if
( !D(
'AccountRecord'
)->consume(
$orderData
[
'total_price'
],
$orderData
[
'user_id'
],
'充值流量,扣除余额'
) ) {
throw
new
\Think\Exception(
"扣除用户余额失败"
,5302);
}
// 将订单设为成功
if
( !
$model
->where(
array
(
'id'
=>
$order_id
))->save(
array
(
'order_status'
=>1)) ) {
throw
new
\Think\Exception(
"订单设置成功失败"
,5303);
}
$model
->commit();
//事务提交
echo
return_result(
array
(
'code'
=>
'2000'
,
'msg'
=>
'充值成功'
));
exit
();
}
catch
(\Think\Exception
$e
) {
$model
->rollback();
//事务回滚
echo
return_result(
array
(
'code'
=>
$e
->getCode(),
'msg'
=>
$e
->getMessage()));
exit
();
}
这样看似没什么问题,一般情况下确实也没什么事,但是高并发情况下,就有问题,具体问题如下:
假如有个用户A同时调用了2次接口,A的账户余额为10,而一次的请求会扣除10的金额,那么,A的2次接口是同时进行的,2次一开始获取的用户余额都是10,这就是问题了
第一次请求,会将A的余额变为0,按理说,第二次请求会因为余额不足而不成功,但是,由于2次请求是并发一起进来的,那么第二次获取的余额上也是10,所以会继续扣款,
此时用户余额为-10
解决方法如下:
将
$uInfo
= D(
'User'
)->where(
array
(
'id'
=>
$uInfo
[
'id'
]))->find();
变成:
$uInfo
= D(
'User'
)->
lock(true)
->where(
array
(
'id'
=>
$uInfo
[
'id'
]))->find();
进行加锁后,成功将问题解决,but,有相对的代价,即性能增加了负担:
这是加锁前的:
这是加锁后的: