1.创建数据表
#1.商品库存表
DROP TABLE IF EXISTS `orders`;
CREATE TABLE `orders` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`number` int(11) DEFAULT NULL COMMENT '总库存',
`sale` int(11) DEFAULT '0' COMMENT '下单数量',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
#添加数据
INSERT INTO `storage` VALUES ('1', '100');
#2.购买记录
DROP TABLE IF EXISTS `orders`;
CREATE TABLE `orders` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`number` int(11) DEFAULT NULL COMMENT '总库存',
`sale` int(11) DEFAULT '0' COMMENT '下单数量',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2.什么并发情况下会出现超卖现象?
截图:
原因是:同一个库存的情况,可能有几个用户同步购买,列如当前库存是99,由于并发的原因,几个用户当前下单的时候库存都是99,导致库存是99的时候,几个用户下单现有库存都是99,然后库存是99的时候产生了几笔订单
3.高并发的情况,防止库存超卖解决方案
4.未使用锁的情况下,库存超卖
创建文件nolock.php
query($sql)->fetch();
$number = $res['number'];
if($number>0)
{
$sale=1; //下单数量
$sql ="insert into `orders` (number,sale) VALUES ($number,$sale)";
$order_id = $pdo->query($sql);
if($order_id)
{
$sql="update storage set `number`=`number`-1 WHERE id=1";
$pdo->query($sql);
}
}else{
echo "库存为空";
}
ab执行压测
ab工具说明:https://www.cnblogs.com/lidabo/p/9077781.html
[root@localhost html]# ab -c 200 -n 200 http://127.0.0.1/nolock.php
5.使用mysql的悲观锁(排它锁),使用for update,防止超卖
创建mysqllock.php
setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); //设置错误模式,发生错误时抛出异常
//开启事务
$pdo->beginTransaction();
//设置不自动提交
$pdo->setAttribute(PDO::ATTR_AUTOCOMMIT,0);
$sql="select `number` from storage where id=1 for update"; //此处加上排他锁 //加上行锁后,如果user1在买书,并且user1的买书过程没有结束,user2就不能执行SELECT查询书籍数量的操作,这样就保证了不会出现只有1本书,却两个人同时买的状况
$res = $pdo->query($sql)->fetch();
$number = $res['number'];
if($number>0)
{
$sale=1; //下单数量
$sql ="insert into `orders` (number,sale) VALUES ($number,$sale)";
$order_id = $pdo->query($sql);
if($order_id)
{
$sql="update storage set `number`=`number`-1 WHERE id=1 "; //加上行锁后,如果user1在买书,并且user1的买书过程没有结束,user2就不能执行SELECT查询书籍数量的操作,这样就保证了不会出现只有1本书,却两个人同时买的状况
$res=$pdo->query($sql);
if(empty($res)){
$pdo->rollBack();
}
}else{
$pdo->rollBack();
}
$pdo->commit();
}else{
$pdo->rollBack();
echo "库存不足";
}
}catch(Exception $e) {
$pdo->rollBack();
die('Transaction Error Message: ' . $e->getMessage());
}
ab执行压测
[root@localhost html]# ab -c 200 -n 200 http://127.0.0.1/mysqllock.php
6.使用redis的悲观锁,利用setnx方法,防止超卖
创建文件redislock.php
connect('127.0.0.1');
$lock_key='numbers_lock'; //锁名称
/**
*@desc redis 加锁方法
*@param $Redis redis对象
*@param $key 锁名称
*@param $expTime 过期时间
*/
function redis_set($Redis,$key,$expTime){
//初步加锁
$islock=$Redis->setnx($key,time()+$expTime);
if($islock){
return true;
}else{
//加锁失败的情况下,判断锁是否已经存在,如果锁存在而却已经过期,那么删除
$val=$Redis->get($key);
if($val && $val
ab执行压测
[root@localhost html]# ab -c 200 -n 200 http://127.0.0.1/redislock.php
7.使用redis乐观锁,利用watch和事务
创建文件rediswatch.php
connect('127.0.0.1');
$lock_key='numbers_lock'; //锁名称
//秒杀商品数量[此数量从storage获取]
$limit=100;
//开始redis监听
$Redis->watch($lock_key);
//获取抢购数量
$count=$Redis->get($lock_key);
if($count>$limit){
exit('活动已结束');
}
//开始redis事务
$Redis->multi();
//业务逻辑
$Redis->incr($lock_key); //模拟一个数量一个数量抢购
//开启事务
$pdo->beginTransaction();
$sale=1; //下单数量
$sql ="insert into `orders` (number,sale) VALUES ($count,$sale)";
$order_id = $pdo->query($sql);
if($order_id){
$sql1="update storage set `number`=`number`-1 WHERE id=1 ";
$res=$pdo->query($sql1);
if(empty($res)){
$pdo->rollBack();
}
}else{
$pdo->rollBack();
}
//默认redis事务不回去管理命令成功还是失败,redis事务不能回滚
$redis_result=$Redis->exec();
if(empty($redis_result)){
exit('网络有问题');
}
//sql执行成功提交事务
$pdo->commit();
ab执行压测
[root@localhost html]# ab -c 200 -n 200 http://127.0.0.1/rediswatch.php