解决类似超卖问题,mysql加锁

来源:互联网 发布:seo 外链 编辑:程序博客网 时间:2024/06/06 16:52
情景:(代码环境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,有相对的代价,即性能增加了负担:

这是加锁前的:



这是加锁后的:


0 0