如果你家店里某商品库存只有100件,现在店庆活动5折优惠大酬宾,假如现在有200个人疯狂涌入你家店里,为了避免发生疯抢和踩踏事件发生,店长您采取了排队限购的办法,1人限购1件,排队先到先买,卖完为止。

这个是实体店我们会看到的场景,100件商品,1人1件,最后200人中只有100人能买到商品,剩下100人只能空手而归。如果您开了家网店,同样你开起了秒杀的活动,可能同时会有1000人通过不同的终端访问你的商品秒杀活动页面,你的商品可以会在瞬间秒杀完毕,库存清零。可是如果网店秒杀活动程序设计出问题,会导致秒杀库存超卖的现象,比如100件库存,实际订单有120件,原因就处在并发同时程序处理的问题上。

其实我们也可以采取排队限购的办法解决网店秒杀活动商品超卖的问题。今天我们给大家讲解采用PHP+Redis+MySQL解决商品秒杀活动中超卖问题。

实现原理

把商品库存数量加到redis队列的num里,下单的时候通过rpop从队列中每次取1件商品,当num为0时,停止下单。

下面我们来看具体实现过程。

创建数据表

我们一共准备3张表,分别是:商品表、订单表、日志表。

1.商品表

CREATE TABLE `hw_goods` (

`id` int(11) NOT NULL AUTO_INCREMENT,

`name` varchar(128) DEFAULT NULL COMMENT '商品名称',

`price` decimal(10,2) DEFAULT NULL COMMENT '商品价格',

`pic` varchar(128) DEFAULT NULL COMMENT '商品图片',

`inventory` int(11) DEFAULT NULL COMMENT '库存',

`created_at` datetime DEFAULT NULL,

`updated_at` datetime DEFAULT NULL,

PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;

-- ----------------------------

-- Records of hw_goods

-- ----------------------------

INSERT INTO `hw_goods` VALUES ('1', 'Apple iPhone 11 (A2223) 64GB 黑色 移动联通电信4G手机 双卡双待', '5499.00', null

我们在商品表中添加商品Apple iPhone 11,设置库存为100。

2.订单表

CREATE TABLE `hw_order` (

`id` int(11) NOT NULL AUTO_INCREMENT,

`order_sn` varchar(32) DEFAULT NULL COMMENT '订单号',

`user_id` int(11) DEFAULT NULL COMMENT '购买者ID',

`status` tinyint(1) DEFAULT '0' COMMENT '订单状态1-已下单,2-已处理,3-已发货,4-已收货,5-订单完成',

`goods_id` int(11) DEFAULT '0' COMMENT '商品id',

`o_num` int(11) DEFAULT NULL COMMENT '购买数量',

`price` int(10) DEFAULT NULL COMMENT '价格,分',

`created_at` datetime DEFAULT NULL,

`updated_at` datetime DEFAULT NULL,

PRIMARY KEY (`id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

3.日志表

CREATE TABLE `hw_order_log` (

`id` int(11) NOT NULL AUTO_INCREMENT,

`status` int(11) DEFAULT '0',

`msg` text,

`created_at` datetime DEFAULT NULL,

PRIMARY KEY (`id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

加入库存队列

我们在Redis中加入商品库存队列。由商品表中我们可知商品Apple iPhone 11库存有100件。我们可以写个脚本将商品库存加入到Redis队列中。

for($i=1; $i <= 100; $i++){

$redis->lpush('num', $i);

}

执行完成后,我们可以看到redis队列。

下单购买

我们建立下单文件Order.php

首先是连接redis和mysql的代码。

class Order

{

private static $redis = null;

private static $pdo = null;

public static function Redis()

{

if (self::$redis == null) {

$redis = new Redis();

$redis->connect('127.0.0.1',6379);

self::$redis = $redis;

}

return self::$redis;

}

public static function mysql()

{

$dbhost = '127.0.0.1'; //数据库服务器

$dbport = 3306; //端口

$dbname = 'demo'; //数据库名称

$dbuser = 'root'; //用户名

$dbpass = ''; //密码

// 连接

try {

$db = new PDO('mysql:host='.$dbhost.';port='.$dbport.';dbname='.$dbname, $dbuser, $dbpass);

$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); //设置错误模式

$db->query('SET NAMES utf8;');

self::$pdo = $db;

} catch (PDOException $e) {

$this->log(0, '连接数据库失败!');

exit;

}

return self::$pdo;

}

}

接着就是抢购下单。我们从商品可以中取出商品信息,然后从redis队列num中rpop出列一个商品数,接着马上处理商品购买的过程。

// 抢购下单

public function goodsOrder()

{

$redis = self::Redis();

$db = self::mysql();

$goodsId = 1;

$sql = "select id,inventory,price from hw_goods where id=".$goodsId;

$stmt = $db->query($sql);

$row = $stmt->fetch(PDO::FETCH_ASSOC);

$redis = self::Redis();

$count = $redis->rpop('num');//每次从num取出1

if($count == 0){

$this->log(0, 'no num redis');

echo '已没有库存';

} else {

$this->doOrder($row, 1);

}

}

上述代码中,如果redis队列数量变成0了,就是没有库存了,这个时候不做订单处理了,如果不是0就要更新库存,生成订单。

// 下单更新库存

public function doOrder($goods, $goodsNum)

{

$orderNo = $this->orderNo();

$number = $goods['inventory'] - $goodsNum;

if ($number < 0) {

$this->log(0, '已没有库存');

echo '已没有库存';

return false;

}

$db = self::mysql();

try {

$db->beginTransaction(); //启动事务

$sql = "INSERT INTO `hw_order` (user_id,order_sn,status,goods_id,o_num,price,created_at) VALUES (:user_id,:order_sn,:status,:goods_id,:sku_id,:o_num,:price,:created_at)";

$stmt = $db->prepare($sql);

$stmt->execute([

':user_id' => rand(1, 500),

':order_sn' => $orderNo,

':status' => 1,

':goods_id' => $goods['id'],

':o_num' => $goodsNum,

':price' => $goods['price'] * 100,

':created_at' => date('Y-m-d H:i:s'),

]);

$sql2 = "update hw_goods set inventory=inventory-".$goodsNum." where inventory>0 and id=".$goods['id'];

$res = $db->exec($sql2);

$db->commit(); //提交事务

$this->log(1, '下单和库存扣减成功');

} catch (Exception $e) {

$db->rollBack(); //回滚事务

$this->log(0, '下单失败');

}

}

在下单过程中,我们采用了MySQL的事物机制,每次当订单表中写入订单数据并且商品表扣除库存-1成功,才算下单完成。

最后附上生产订单号的代码,以及日志记录代码。

// 生成订单号

public function orderNo()

{

return date('Ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);

}

// 保存日志

public function log($status, $msg)

{

$db = self::mysql();

$sql = "INSERT INTO `hw_order_log` (status,msg,created_at) VALUES (:status,:msg,:created_at)";

$stmt = $db->prepare($sql);

$stmt->execute([

':msg' => $msg,

':status' => $status,

':created_at' => date('Y-m-d H:i:s')

]);

}

调用下单代码:

$order = new Order();

$order->goodsOrder();

详细代码请点击文章上部的下载按钮,移动版用户不提供下载。

并发测试

我们Apache的ab测试,ab是apachebench命令的缩写,是Apache自带的压力测试工具,假如你安装了Apache软件后,在他的bin目录下可以找到ab这个程序。

保证你的order.php在你的站点能访问到,然后启动ab测试,输入以下命令:

ab -n 1000 -c 200 http://localhost/order.php

(-n发出1000个请求,-c模拟200并发,请求数要大于或等于并发数。相当1000人同时访问,后面是测试url )。

执行结果如图:

验证结果

分别查看商品表hw_goods,检验库存字段inventory是否由100变成0了。

查看订单表hw_order,查询该商品的订单总数是否为100。

查看日志表hw_order_log,查询状态status为1的订单日志记录是否是100条,其余的状态均为0。

经验证,库存为0,订单总数为100,并没有出现超卖的现象。

php mysql商品数量购买减少_PHP+Redis+MySQL商品秒杀与超卖讲解相关推荐

  1. Redis实战——优惠券秒杀(超卖问题)

    1 实现优惠券秒杀功能 下单时需要判断两点:1.秒杀是否开始或者结束2.库存是否充足 所以,我们的业务逻辑如下 1. 通过优惠券id获取优惠券信息 2.判断秒杀是否开始,如果未返回错误信息 3.判断秒 ...

  2. Redis解决商品秒杀与超卖

    这个是实体店我们会看到的场景,100件商品,1人1件,最后200人中只有100人能买到商品,剩下100人只能空手而归.如果您开了家网店,同样你开起了秒杀的活动,可能同时会有1000人通过不同的终端访问 ...

  3. OpenCart 2.x 系统商品数量库存减少逻辑

    2019独角兽企业重金招聘Python工程师标准>>> OpenCart 1.5.x以及2.x版本中,在后台编辑商品时,有设定是否减少库存(subtract)选项,如果选中了,则在后 ...

  4. Spring Boot + redis解决商品秒杀库存超卖,看这篇文章就够了

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者:涛哥谈篮球 来源:toutiao.com/i68366119 ...

  5. redis如何解决秒杀超卖java_Spring Boot + redis解决商品秒杀库存超卖,看这篇文章就够了...

    作者:涛哥谈篮球 来源:toutiao.com/i6836611989607809548 问题描述 在众多抢购活动中,在有限的商品数量的限制下如何保证抢购到商品的用户数不能大于商品数量,也就是不能出现 ...

  6. mysql 9.0创建数据库_PHP与MySQL学习笔记9:创建Web数据库

    1.在服务器上部署MySQL服务基本步骤合注意点 1)安装MySQL(命令安装.安装包安装等) 2)考虑是否需要一个独立的操作系统用户权限来运行MySQL程序. 3)路径的设置 4)root密码 (附 ...

  7. apache php mysql是长连接吗_php关于mysql长连接问题

    1.当 函数 mysql_connect 的前三个参数(server username password)相同,并且第四个参数(new_link)不传递时候,重复调用mysql_connect 是会返 ...

  8. 【Redis】实战篇:优惠卷秒杀 (库存超卖问题、一人一单问题)

    文章目录 3.1 全局唯一ID 3.2 -Redis实现全局唯一Id 3.3 添加优惠卷 3.4 实现秒杀下单 3.5 库存超卖问题分析 3.6 乐观锁解决超卖问题 3.7 优惠券秒杀-一人一单 3. ...

  9. php读取mysql数据无法修改时间_php设置mysql查询读取数据的超时时间

    php可以设置mysql查询的超时时间估计大家不知道吧,一般都直接在mysql中进行设置了,下面我们来为各位介绍一下php设置mysql查询读取数据的超时时间吧. 现象:php能通过代理正常连接到my ...

  10. php mysql千万级数据修改_PHP 结合 MySQL 千万级数据处理

    mysql分表思路 一张一亿的订单表,可以分成五张表,这样每张表就只有两千万数据,分担了原来一张表的压力,分表需要根据某个条件进行分,这里可以根据地区来分表,需要一个中间件来控制到底是去哪张表去找到自 ...

最新文章

  1. 一个复杂系统的拆分改造,压力真大!
  2. hp远程桌面服务器,hp服务器通过ilo远程安装操作系统
  3. 【PHP】字符串去空格并将每个单词首字母转换成大写de多种解法
  4. Apache理论与实战
  5. linux中安装vsftpd出现的问题
  6. mysql5.7.20+初始化_MySQL5.7.28 初始化数据库
  7. 读“NoSQL注入的分析和缓解”之摘录
  8. sql语句ding_mybatis plus 写sql语句
  9. VSTO学习笔记(四)从SharePoint 2010中下载文件
  10. 4.高性能MySQL --- Schema与数据类型优化
  11. Spring揭秘(一)spring框架的由来
  12. 【毕设教学】单片机控制步进电机
  13. Ubuntu 下串口调试工具
  14. C语言读取文件内容创建二叉树
  15. 互联网科普-什么是淘宝
  16. [1007]魔法少女小Scarlet(洛谷 P4924)
  17. 路由器设置显示服务器拒绝访问,路由器设置服务器拒绝访问
  18. html js动态时间轴,jQuery时间轴插件timeline.js
  19. 记录错误:cv2.error: OpenCV(4.4.0) C:\Users\appveyor\AppData\Local\Temp\1\pip-req-build-h4wtvo23\opencv\m
  20. 问题 A: Jugs

热门文章

  1. 怎么用sql按条件把表分离_在做sqlserver数据库sql优化时,这25条事项需要注意
  2. 现控笔记(五)稳定性与Lyapunov方法
  3. Flutter报错 使用Column等容器包裹ListView报错的问题
  4. L1-017 到底有多二 (15 分) — 团体程序设计天梯赛
  5. Can't update: no tracked branch No tracked branch configured for branch dev.
  6. HTMLCSS学习笔记(三)----标签类型转换、样式重置
  7. JavaScript实现省市选择功能,jQuery,Json
  8. Python Project Euler 013:100个50位数和
  9. H3C交换机创建用户
  10. 按之字形顺序打印二叉树(C++)