加入收藏 | 设为首页 | 会员中心 | 我要投稿 开发网_郴州站长网 (http://www.0735zz.com/)- 云通信、区块链、物联设备、云计算、站长网!
当前位置: 首页 > 站长学院 > PHP教程 > 正文

PHP高并发情形下怎么避免商品库存超卖

发布时间:2022-07-09 13:02:31 所属栏目:PHP教程 来源:互联网
导读:商城系统中,抢购和秒杀是很常见的营销场景,在一定时间内有大量的用户访问商场下单,主要需要解决的问题有两个: 高并发对数据库产生的压力; 竞争状态下如何解决商品库存超卖; 高并发对数据库产生的压力 对于第一个问题,使用缓存来处理,避免直接操作数
  商城系统中,抢购和秒杀是很常见的营销场景,在一定时间内有大量的用户访问商场下单,主要需要解决的问题有两个:
 
  高并发对数据库产生的压力;
 
  竞争状态下如何解决商品库存超卖;
 
  高并发对数据库产生的压力
 
  对于第一个问题,使用缓存来处理,避免直接操作数据库,例如使用 Redis。
 
  竞争状态下如何解决商品库存超卖
 
  对于第二个问题,需要重点说明。
 
  常规写法:查询出对应商品的库存,判断库存数量否大于 0,然后执行生成订单等操作,但是在判断库存是否大于 0 处,如果在高并发下就会有问题,导致库存量出现负数。
 
  测试表 sql
 
  把如下表数据导入到数据库中
 
  
 
 
  /*
 
  Navicat MySQL Data Transfer
 
   
 
  Source Server         : 01 本地localhost
 
  Source Server Version : 50553
 
  Source Host           : localhost:3306
 
  Source Database       : test
 
   
 
  Target Server Type    : MYSQL
 
  Target Server Version : 50553
 
  File Encoding         : 65001
 
   
 
  Date: 2020-11-06 14:31:35
 
  */
 
   
 
  SET FOREIGN_KEY_CHECKS=0;
 
   
 
  -- ----------------------------
 
  -- Table structure for products
 
  -- ----------------------------
 
  DROP TABLE IF EXISTS `products`;
 
  CREATE TABLE `products` (
 
    `id` int(10) NOT NULL AUTO_INCREMENT COMMENT 'ID',
 
    `title` varchar(50) DEFAULT NULL COMMENT '货品名称',
 
    `store` int(11) DEFAULT '0' COMMENT '货品库存',
 
    PRIMARY KEY (`id`)
 
  ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='货品表';
 
   
 
  -- ----------------------------
 
  -- Records of products
 
  -- ----------------------------
 
  INSERT INTO `products` VALUES ('1', '稻花香大米', '20');
 
   
 
  -- ----------------------------
 
  -- Table structure for order_log
 
  -- ----------------------------
 
  DROP TABLE IF EXISTS `order_log`;
 
  CREATE TABLE `order_log` (
 
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
 
    `content` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '日志内容',
 
    `c_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
 
    PRIMARY KEY (`id`)
 
  ) ENGINE=MyISAM DEFAULT CHARSET=latin1;
 
   
 
  -- ----------------------------
 
  -- Table structure for order
 
  -- ----------------------------
 
  DROP TABLE IF EXISTS `order`;
 
  CREATE TABLE `order` (
 
    `oid` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '订单号',
 
    `product_id` int(11) DEFAULT '0' COMMENT '商品ID',
 
    `number` int(11) DEFAULT '0' COMMENT '购买数量',
 
    `c_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
 
    PRIMARY KEY (`oid`)
 
  ) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT='订单表';
 
  下单处理代码
 
  
 
  <?php
 
   
 
  db();
 
  global $con;
 
   
 
  //step1 接收下单参数
 
  $product_id = 1;// 商品ID
 
  $buy_num = 1;// 购买数量
 
   
 
  //step2 查询商品信息
 
  $sql = "select * from products where id={$product_id}";
 
  $result = mysqli_query($con, $sql);
 
  $row = mysqli_fetch_assoc($result);
 
   
 
  //step3 判断商品下单数量是否大于商品库存数量
 
  //此处在高并发下,可能出现上一个下单后还没来得及更新库存,下一个下单判断库存数不是最新的库存
 
  if ($row['store'] > 0) {
 
   
 
      sleep(1);
 
      //step4 更新商品库存数量(减去下单数量)
 
      $sql = "update products set store=store-{$buy_num} where id={$product_id}";
 
      if (mysqli_query($con, $sql)) {
 
          echo "更新成功";
 
          //step5 生成订单号创建订单
 
          $oid = build_order_no();
 
          create_order($oid, $product_id, $buy_num);
 
          insertLog('库存减少成功,下单成功');
 
      } else {
 
          echo "更新失败";
 
          insertLog('库存减少失败');
 
      }
 
   
 
  } else {
 
      echo "没有库存";
 
      insertLog('库存不够');
 
  }
 
   
 
  function db()
 
  {
 
      global $con;
 
      $con = new mysqli('localhost','root','root','test');
 
      if (!$con) {
 
          echo "数据库连接失败";
 
      }
 
  }
 
   
 
  /**
 
   * 生成唯一订单号
 
   */
 
  function build_order_no()
 
  {
 
      return date('Ymd') . str_pad(mt_rand(1, 99999), 5, '0', STR_PAD_LEFT);
 
  }
 
   
 
  function create_order($oid, $product_id, $number)
 
  {
 
      global $con;
 
      $sql = "INSERT INTO `order` (oid, product_id, number) values('$oid', '$product_id', '$number')";
 
      mysqli_query($con, $sql);
 
  }
 
   
 
  /**
 
   * 记录日志
 
   */
 
  function insertLog($content)
 
  {
 
      global $con;
 
      $sql = "INSERT INTO `order_log` (content) values('$content')";
 
      mysqli_query($con, $sql);
 
  }
 
  将库存字段字段设为 unsigned
 
  因为库存字段不能为负数,在下单后更新商品库存时,如果出现负数将返回 false
 
  
 
 
  <?php
 
  db();
 
  global $con;
 
   
 
  //step1 接收下单参数
 
  $product_id = 1;// 商品ID
 
  $buy_num = 1;// 购买数量
 
   
 
  //step2 查询商品信息
 
  $sql = "select * from products where id={$product_id} for UPDATE";//利用for update 开启行锁
 
  $result = mysqli_query($con, $sql);
 
  $row = mysqli_fetch_assoc($result);
 
   
 
  //step3 判断商品下单数量是否大于商品库存数量
 
  if ($row['store'] > 0) {
 
   
 
      sleep(1);
 
      //step4 更新商品库存数量(减去下单数量)
 
      $sql = "update products set store=store-{$buy_num} where id={$product_id}";
 
      if (mysqli_query($con, $sql)) {
 
          echo "更新成功";
 
          //step5 生成订单号创建订单
 
          $oid = build_order_no();
 
          create_order($oid, $product_id, $buy_num);
 
          insertLog('库存减少成功,下单成功');
 
      } else {
 
          // 如果出现负数将返回false
 
          echo "更新失败";
 
          insertLog('库存减少失败');
 
      }
 
  } else {
 
      //商品已经抢购完
 
      echo "没有库存";
 
      insertLog('库存不够');
 
  }
 
   
 
  function db()
 
  {
 
      global $con;
 
      $con = new mysqli('localhost','root','root','test');
 
      if (!$con) {
 
          echo "数据库连接失败";
 
      }
 
  }
 
   
 
  /**
 
   * 生成唯一订单号
 
   */
 
  function build_order_no()
 
  {
 
      return date('Ymd') . str_pad(mt_rand(1, 99999), 5, '0', STR_PAD_LEFT);
 
  }
 
   
 
  function create_order($oid, $product_id, $number)
 
  {
 
      global $con;
 
      $sql = "INSERT INTO `order` (oid, product_id, number) values('$oid', '$product_id', '$number')";
 
      mysqli_query($con, $sql);
 
  }
 
   
 
  /**
 
   * 记录日志
 
   */
 
  function insertLog($content)
 
  {
 
      global $con;
 
      $sql = "INSERT INTO `order_log` (content) values('$content')";
 
      mysqli_query($con, $sql);
 
  }
 
  使用 mysql 的事务,锁住操作的行
 
  在下单处理过程中,使用 mysql 的事务将正在下单商品行数据锁定
 
  
 
 
 
  <?php
 
  db();
 
  global $con;
 
   
 
  //step1 接收下单参数
 
  $product_id = 1;// 商品ID
 
  $buy_num = 1;// 购买数量
 
   
 
  mysqli_query($con, "BEGIN"); //开始事务
 
   
 
  //step2 查询商品信息
 
  $sql = "select * from products where id={$product_id} for UPDATE";//利用for update 开启行锁
 
  $result = mysqli_query($con, $sql);
 
  $row = mysqli_fetch_assoc($result);
 
   
 
  //step3 判断商品下单数量是否大于商品库存数量
 
  if ($row['store'] > 0) {
 
   
 
      sleep(1);
 
      //step4 更新商品库存数量(减去下单数量)
 
      $sql = "update products set store=store-{$buy_num} where id={$product_id}";
 
      if (mysqli_query($con, $sql)) {
 
          echo "更新成功";
 
          //step5 生成订单号创建订单
 
          $oid = build_order_no();
 
          create_order($oid, $product_id, $buy_num);
 
          insertLog('库存减少成功,下单成功');
 
          mysqli_query($con, "COMMIT");//事务提交即解锁
 
      } else {
 
          echo "更新失败";
 
          insertLog('库存减少失败');
 
          mysqli_query($con, "ROLLBACK");//事务回滚即解锁
 
      }
 
  } else {
 
      //商品已经抢购完
 
      echo "没有库存";
 
      insertLog('库存不够');
 
      mysqli_query($con, "ROLLBACK");//事务回滚即解锁
 
  }
 
   
 
  function db()
 
  {
 
      global $con;
 
      $con = new mysqli('localhost','root','root','test');
 
      if (!$con) {
 
          echo "数据库连接失败";
 
      }
 
  }
 
   
 
  /**
 
   * 生成唯一订单号
 
   */
 
  function build_order_no()
 
  {
 
      return date('Ymd') . str_pad(mt_rand(1, 99999), 5, '0', STR_PAD_LEFT);
 
  }
 
   
 
  function create_order($oid, $product_id, $number)
 
  {
 
      global $con;
 
      $sql = "INSERT INTO `order` (oid, product_id, number) values('$oid', '$product_id', '$number')";
 
      mysqli_query($con, $sql);
 
  }
 
   
 
  /**
 
   * 记录日志
 
   */
 
  function insertLog($content)
 
  {
 
      global $con;
 
      $sql = "INSERT INTO `order_log` (content) values('$content')";
 
      mysqli_query($con, $sql);
 
  }
 
  使用非阻塞的文件排他锁
 
  在处理下单请求的时候,用 flock 锁定一个文件,如果锁定失败说明有其他订单正在处理,此时要么等待要么直接提示用户” 服务器繁忙”,计数器存储抢购的商品数量,避免查询数据库。
 
  阻塞 (等待) 模式:并发时,当有第二个用户请求时,会等待第一个用户请求完成、释放锁,获得文件锁之后,程序才会继续运行下去。
 
 
  
 
  <?php
 
  db();
 
  global $con;
 
   
 
  //step1 接收下单参数
 
  $product_id = 1;// 商品ID
 
  $buy_num = 1;// 购买数量
 
   
 
  $fp = fopen('lock.txt', 'w');
 
  if (flock($fp, LOCK_EX)) {   //文件独占锁,阻塞
 
      //step2 查询商品信息
 
      $sql = "select * from products where id={$product_id}";
 
      $result = mysqli_query($con, $sql);
 
      $row = mysqli_fetch_assoc($result);
 
   
 
      //step3 判断商品下单数量是否大于商品库存数量
 
      if ($row['store'] > 0) {
 
          //处理订单
 
          sleep(1);
 
          //step4 更新商品库存数量(减去下单数量)
 
          $sql = "update products set store=store-{$buy_num} where id={$product_id}";
 
          if (mysqli_query($con, $sql)) {
 
              echo "更新成功";
 
              //step5 生成订单号创建订单
 
              $oid = build_order_no();
 
              create_order($oid, $product_id, $buy_num);
 
              insertLog('库存减少成功,下单成功');
 
          } else {
 
              echo "更新失败";
 
              insertLog('库存减少失败');
 
          }
 
      } else {
 
          //商品已经抢购完
 
          echo "没有库存";
 
          insertLog('库存不够');
 
      }
 
      flock($fp, LOCK_UN); //释放锁
 
   
 
  }
 
  fclose($fp);
 
   
 
  function db()
 
  {
 
      global $con;
 
      $con = new mysqli('localhost','root','root','test');
 
      if (!$con) {
 
          echo "数据库连接失败";
 
      }
 
  }
 
   
 
  /**
 
   * 生成唯一订单号
 
   */
 
  function build_order_no()
 
  {
 
      return date('Ymd') . str_pad(mt_rand(1, 99999), 5, '0', STR_PAD_LEFT);
 
  }
 
   
 
  function create_order($oid, $product_id, $number)
 
  {
 
      global $con;
 
      $sql = "INSERT INTO `order` (oid, product_id, number) values('$oid', '$product_id', '$number')";
 
      mysqli_query($con, $sql);
 
  }
 
   
 
  /**
 
   * 记录日志
 
   */
 
  function insertLog($content)
 
  {
 
      global $con;
 
      $sql = "INSERT INTO `order_log` (content) values('$content')";
 
      mysqli_query($con, $sql);
 
  }
 
  非阻塞模式:并发时,第一个用户请求,拿得文件锁之后。后面请求的用户直接返回系统繁忙,请稍后再试

(编辑:开发网_郴州站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    热点阅读