设为首页收藏本站

安徽论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 7208|回复: 0

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

[复制链接]

63

主题

789

回帖

1382

积分

金牌会员

Rank: 6Rank: 6

积分
1382
发表于 2022-3-26 10:56:51 | 显示全部楼层 |阅读模式
网站内容均来自网络,本站只提供信息平台,如有侵权请联系删除,谢谢!
悲观锁介绍(百科):

悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。
使用场景举例:以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.   `name` varchar(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. 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. namespace app\controller;

  3. use app\BaseController;
  4. use think\facade\Db;

  5. class Test extends BaseController
  6. {

  7.     /**
  8.      * 不加锁
  9.      * @return string|void
  10.      */
  11.     public function test_1()
  12.     {
  13.         $num = 1;
  14.         $goods_id = 1;
  15.         Db::startTrans();
  16.         try {
  17.             $where = [];
  18.             $where['id'] = $goods_id;
  19.             $where['status'] = 1;
  20.             $goods_info = Db::table('goods')->where($where)->find();
  21.             if (empty($goods_info)) {
  22.                 return '商品不存在';
  23.             }
  24.             $total = $goods_info['total'];
  25.             $sell = $goods_info['sell'];
  26.             if ($total < $num) {
  27.                 return '库存不足';
  28.             }
  29.             $data['total'] = $total-$num;
  30.             $data['sell'] = $sell+$num;
  31.             $res = Db::table('goods')->where(['id'=>$goods_id])->update($data);
  32.             $order_data = [];
  33.             $order_data['uid'] = rand(1000,9999);
  34.             $order_data['status'] = 1;
  35.             $order_data['create_time'] = date('Y-m-d H:i:s');
  36.             $order_data['goods_id'] = $goods_id;
  37.             $order_data['order_no'] = date('YmdHis').rand(1000,10000);
  38.             $order_res = Db::table('orders')->insert($order_data);
  39.             Db::commit();
  40.         } catch (\Exception $e) {
  41.             // 回滚事务
  42.             Db::rollback();
  43.             echo $e->getMessage();
  44.             exit('rollback');
  45.         }
  46.         echo '请求成功';
  47.     }

  48.     /**
  49.      * 加锁--悲观锁
  50.      * @return string|void
  51.      */
  52.     public function test_2()
  53.     {
  54.         $num = 1;
  55.         $goods_id = 1;
  56.         Db::startTrans();
  57.         try {
  58.             $where = [];
  59.             $where['id'] = $goods_id;
  60.             $where['status'] = 1;
  61.             $goods_info = Db::table('goods')->lock(true)->where($where)->find();
  62.             if (empty($goods_info)) {
  63.                 return '商品不存在';
  64.             }
  65.             $total = $goods_info['total'];
  66.             $sell = $goods_info['sell'];
  67.             if ($total < $num) {
  68.                 return '库存不足';
  69.             }
  70.             $data['total'] = $total-$num;
  71.             $data['sell'] = $sell+$num;
  72.             $res = Db::table('goods')->where(['id'=>$goods_id])->update($data);
  73.             $order_data = [];
  74.             $order_data['uid'] = rand(1000,9999);
  75.             $order_data['status'] = 1;
  76.             $order_data['goods_id'] = $goods_id;
  77.             $order_data['order_no'] = date('YmdHis').rand(1000,10000);
  78.             $order_data['create_time'] = date('Y-m-d H:i:s');
  79.             $order_res = Db::table('orders')->insert($order_data);
  80.             Db::commit();
  81.         } catch (\Exception $e) {
  82.             // 回滚事务
  83.             Db::rollback();
  84.             echo $e->getMessage();
  85.             exit('rollback');

  86.         }
  87.         echo '请求成功';
  88.     }

  89. }
复制代码
使用jmeter工具测试,创建线程测试组:
关于使用jmeter创建测试高并发例子,可查看:使用JMeter进行高并发测试_左右..的博客-CSDN博客_jmeter高并发测试
这里模拟1s内1000个用户同时访问

 创建http请求:

添加察看结果树:

 测试开始:
100个商品不加锁的结果:

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

 加锁,得到解决。
到此这篇关于thinkphp6使用mysql悲观锁解决商品超卖问题的实现的文章就介绍到这了,更多相关thinkphp6 商品超卖内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
                                                        
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
免责声明
1. 本论坛所提供的信息均来自网络,本网站只提供平台服务,所有账号发表的言论与本网站无关。
2. 其他单位或个人在使用、转载或引用本文时,必须事先获得该帖子作者和本人的同意。
3. 本帖部分内容转载自其他媒体,但并不代表本人赞同其观点和对其真实性负责。
4. 如有侵权,请立即联系,本网站将及时删除相关内容。
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表