悲观锁解决高并发出现的数据不一致问题

这里有三 个概念:悲观锁、高并发、数据不一致

高并发:

高并发是指在同一个时间点,有很多用户同时的访问URL地址,比如:淘宝的双11,双12,就会产生高并发。

数据不一致:

一般导致数据库中数据不一致的根本原因有三种情况。

第一种情况:数据冗余

假如数据库中两个表都放了用户的地址,在用户的地址发生改变时,如果只更新了一个表的数据,那么两个表就有了不一致的数据。

第二种情况:并发控制不当 

假如在飞机票订票系统中,如果两个购票点同时查询某张机票的订购情况,而且分别为订购了这张机票,如果并发控制不当,就会造成同一张机票卖给两个用户的情况。由于系统没有进行并发控制或者并发控制不当,造成数据不一致。

第三中情况:故障和错误

如果软硬件出现故障或者操作错误导致数据丢失或数据损坏,引起数据不一致。因此我们需要提供数据库维护和数据库数据恢复的一些措施。

悲观锁:

在关系数据库管理系统中,悲观并发控制(悲观锁,PCC)是一种并发控制的方法。它可以阻止一个事务以影响其他用户的方式来修改数据。如果一个事务执行的操作的每行数据应用了锁,那只有当这个事务锁释放,其他事务才能够执行与该锁冲突的操作。

悲观锁,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度(悲观),因此在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,一般依靠数据库提供的锁机制。

有关mysql的锁机制,可以参见下面的文档:

https://www.cnblogs.com/leedaily/p/8378779.html

案例

商品goods表,假设商品的id为1,购买数量为1,status为1表示上架中,2表示下架。现在用户购买此商品,在不是高并发的情况下处理逻辑是:

  1. 查找此商品的信息;
  2. 检查商品库存是否大于购买数量;
  3. 修改商品库存和销量;
  4. 订单表orders中添加一条记录

上面这种场景在高并发访问的情况下很可能会出现问题。如果商品库存是100个,高并发的情况下可能会有1000个同时访问,在到达第2步的时候,都会检测通过。这样会出现商品库存是-900个的情况。显然着不满足需求!!!

实施方案

数据表goods表结构

测试数据:

订单表orders表结构

测试数据

以下使用THINKPHP5.0编写代码,实现顾客购买的行为

在index模块中,定义一个buy控制器

<?php


namespace app\index\controller;


use think\Controller;
use think\Db;

class Buy extends Controller
{
    /**
     * thinkphp使用悲观锁。悲观锁需要配合事务一起使用
     * 商品表。购买数量为1,先锁定该商品,不让其他操作减库存。
     */
    public function lock(){
        $num = 1;
        $goods_id = 1;

        Db::execute('set autocommit=0');
        Db::startTrans();
        //使用主键或者其他索引字段时为行级锁,否则为表级锁
        $where['id'] = $goods_id;
        $where['status'] = 1;
        $goods_info = Db::name('goods')->lock(true)->where($where)->find();
        if(empty($goods_info)){
            return '商品不存在';
        }
        $total = $goods_info['total'];  //商品库存
        $sell = $goods_info['sell'];    //商品销量
        if($total<$num){
            return '库存不足';
        }
        $data['total'] = $total-$num;
        $data['sell'] = $sell+$num;
        $res = Db::name('goods')->where(array('id'=>$goods_id))->update($data);
        if($res){
            $order_data['goods_id'] = $goods_id;
            $order_data['num'] = $num;
            $order_data['create_time'] = time();
            $order_res = Db::name('orders')->insert($order_data);
            if($order_res){
                Db::commit();
            }else{
                Db::rollback();
            }
        }else{
            Db::rollback();
        }
        return '执行完毕';

    }



    /**
     * 不加锁的情况
     * @return string
     */
    public function unlock(){
        $num = 1;
        $goods_id = 1;

        DB::execute('set autocommit=0');
        Db::startTrans();
        $where['id'] = $goods_id;
        $where['status'] = 1;
        $goods_info = Db::name('goods')->where($where)->find();
        if(empty($goods_info)){
            return '商品不存在';
        }
        $total = $goods_info['total'];  //商品库存
        $sell = $goods_info['sell'];    //商品销量
        if($total<$num){
            return '库存不足';
        }
        $data['total'] = $total-$num;
        $data['sell'] = $sell+$num;
        $res = Db::name('goods')->where(array('id'=>$goods_id))->update($data);
        if($res){
            $order_data['goods_id'] = $goods_id;
            $order_data['num'] = $num;
            $order_data['create_time'] = time();
            $order_res = Db::name('orders')->insert($order_data);
            if($order_res){
                Db::commit();
            }else{
                Db::rollback();
            }
        }else{
            Db::rollback();
        }
        return '执行完毕';

    }

}

在这个控制器中有两个方法,lock()方法使用了悲观锁处理数据,unlock()方法没有使用悲观锁。两个方法都使用了事务处理。

在thinkphp5.0中:
Lock方法是用于数据库的锁机制,如果在查询或者执行操作的时候使用:
lock(true);
就会自动在生成的SQL语句最后加上 FOR UPDATE 或者 FOR UPDATE NOWAIT (Oracle数据库)。
在没有高并发的情况下,lock()方法和unlcok()方法都是一样的,可以保证数据的一致性。
 
在浏览器中输入:
或者:
 
然后检查数据表中数据
goods表的数据
 
 

订单表Orders中的数据

 商品表goods中卖出了5件商品,订单表中有5条记录,符合数据的一致性。

在高并发的情况下,不使用悲观锁的unlock()方法就会出现数据不一致的情况。

下面借助apache下的ab.exe生成高并的请求

 查看一下数据表

 在同时有100人请求的情况 下,商品的库存减少了31个,但订单却增加了40个

你可以使用ab.exe 同时生成1000个请求,看看数据表goods和orders的数据是否一致。

接下来,我们测试使用悲观锁的数据处理

查看数据表的情况:

使用悲观锁的高并发测试结果:商品 goods表卖出50,订单orders中共有50条记录,数据保持了一致性。

发布了159 篇原创文章 · 获赞 45 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/lsmxx/article/details/104100872