当前位置:首页 > CMS教程 > Thinkphp > 列表

thinkphp6使用mysql悲观锁解决商品超卖问题的实现

发布:smiling 来源: PHP粉丝网  添加日期:2022-05-18 08:59:49 浏览: 评论:0 

这篇文章主要介绍了thinkphp6使用mysql悲观锁解决商品超卖问题的实现

悲观锁介绍(百科):

悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。

使用场景举例:以MySQL InnoDB为例

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

查找此商品的信息;

检查商品库存是否大于购买数量;

修改商品库存和销量;

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

商品表结构:

  1. CREATE TABLE `goods` ( 
  2.   `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 
  3.   `namevarchar(100) COLLATE utf8_unicode_ci NOT NULL DEFAULT ''
  4.   `status` tinyint(1) NOT NULL DEFAULT '1'
  5.   `total` int(11) NOT NULL DEFAULT '0'
  6.   `sell` int(11) NOT NULL DEFAULT '100'
  7.   `price` decimal(10,2) NOT NULL
  8.   PRIMARY KEY (`id`) 
  9. ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; 
  10.    
  11. INSERT INTO `test`.`goods`(`id`, `name`, `status`, `total`, `sell`, `price`) VALUES (1, '商品', 1, 0, 100, 15.00); 

订单表结构:

  1. CREATE TABLE `orders` ( 
  2.   `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 
  3.   `uid` int(11) NOT NULL DEFAULT '0'
  4.   `create_time` datetime NOT NULL
  5.   `status` tinyint(1) NOT NULL DEFAULT '1'
  6.   `goods_id` int(11) NOT NULL DEFAULT '0'
  7.   `order_no` varchar(200) COLLATE utf8_unicode_ci NOT NULL DEFAULT ''
  8.   PRIMARY KEY (`id`) 
  9. ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; 

使用悲观锁处理。

当我们在查询出goods信息后就把当前的数据锁定,直到我们修改完毕后再解锁。那么在这个过程中,因为goods被锁定了,就不会出现有第三者来对其进行修改了。

注:要使用悲观锁,我们必须关闭mysql数据库的自动提交属性,因为MySQL默认使用autocommit模式,也就是说,当你执行一个更新操作后,MySQL会立刻将结果进行提交。thinkphp6中使用事务,手动进行提交回滚。

  1. <?php 
  2.    
  3. namespace app\controller; 
  4.    
  5. use app\BaseController; 
  6. use think\facade\Db; 
  7.    
  8. class Test extends BaseController 
  9.    
  10.     /** 
  11.      * 不加锁 
  12.      * @return string|void 
  13.      */ 
  14.     public function test_1() 
  15.     { 
  16.         $num = 1; 
  17.         $goods_id = 1; 
  18.         Db::startTrans(); 
  19.         try { 
  20.             $where = []; 
  21.             $where['id'] = $goods_id
  22.             $where['status'] = 1; 
  23.             $goods_info = Db::table('goods')->where($where)->find(); 
  24.             if (emptyempty($goods_info)) { 
  25.                 return '商品不存在'
  26.             } 
  27.             $total = $goods_info['total']; 
  28.             $sell = $goods_info['sell']; 
  29.             if ($total < $num) { 
  30.                 return '库存不足'
  31.             } 
  32.             $data['total'] = $total-$num
  33.             $data['sell'] = $sell+$num
  34.             $res = Db::table('goods')->where(['id'=>$goods_id])->update($data); 
  35.             $order_data = []; 
  36.             $order_data['uid'] = rand(1000,9999); 
  37.             $order_data['status'] = 1; 
  38.             $order_data['create_time'] = date('Y-m-d H:i:s'); 
  39.             $order_data['goods_id'] = $goods_id
  40.             $order_data['order_no'] = date('YmdHis').rand(1000,10000); 
  41.             $order_res = Db::table('orders')->insert($order_data); 
  42.             Db::commit(); 
  43.         } catch (\Exception $e) { 
  44.             // 回滚事务 
  45.             Db::rollback(); 
  46.             echo $e->getMessage(); 
  47.             exit('rollback'); 
  48.         } 
  49.         echo '请求成功'
  50.     } 
  51.    
  52.     /** 
  53.      * 加锁--悲观锁 
  54.      * @return string|void 
  55.      */ 
  56.     public function test_2() 
  57.     { 
  58.         $num = 1; 
  59.         $goods_id = 1; 
  60.         Db::startTrans(); 
  61.         try { 
  62.             $where = []; 
  63.             $where['id'] = $goods_id
  64.             $where['status'] = 1; 
  65.             $goods_info = Db::table('goods')->lock(true)->where($where)->find(); 
  66.             if (emptyempty($goods_info)) { 
  67.                 return '商品不存在'
  68.             } 
  69.             $total = $goods_info['total']; 
  70.             $sell = $goods_info['sell']; 
  71.             if ($total < $num) { 
  72.                 return '库存不足'
  73.             } 
  74.             $data['total'] = $total-$num
  75.             $data['sell'] = $sell+$num
  76.             $res = Db::table('goods')->where(['id'=>$goods_id])->update($data); 
  77.             $order_data = []; 
  78.             $order_data['uid'] = rand(1000,9999); 
  79.             $order_data['status'] = 1; 
  80.             $order_data['goods_id'] = $goods_id
  81.             $order_data['order_no'] = date('YmdHis').rand(1000,10000); 
  82.             $order_data['create_time'] = date('Y-m-d H:i:s'); 
  83.             $order_res = Db::table('orders')->insert($order_data); 
  84.             Db::commit(); 
  85.         } catch (\Exception $e) { 
  86.             // 回滚事务 
  87.             Db::rollback(); 
  88.             echo $e->getMessage(); 
  89.             exit('rollback'); 
  90.    
  91.         } 
  92.         echo '请求成功'
  93.     } 
  94.    

使用jmeter工具测试,创建线程测试组:

关于使用jmeter创建测试高并发例子,可查看:使用JMeter进行高并发测试_左右..的博客-CSDN博客_jmeter高并发测试

这里模拟1s内1000个用户同时访问

thinkphp6使用mysql悲观锁解决商品超卖问题的实现

创建http请求:

thinkphp6使用mysql悲观锁解决商品超卖问题的实现

添加察看结果树:

thinkphp6使用mysql悲观锁解决商品超卖问题的实现

测试开始:

100个商品不加锁的结果:

thinkphp6使用mysql悲观锁解决商品超卖问题的实现

100个商品库存,生成订单187个,超卖87个商品,这在项目开发中是绝对不允许的。

100个商品,加锁结果:

thinkphp6使用mysql悲观锁解决商品超卖问题的实现

加锁,得到解决。

Tags: thinkphp6 mysql悲观锁

分享到: