PHP商品秒杀问题解决方案实例详解【mysql与redis】(2)

乐观锁

乐观锁的方案在读取数据是并没有加排他锁,而是通过一个每次更新都会自增的version字段来解决,多个进程读取到相同num,然后都能更新成功的问题。在每个进程读取num的同时,也读取version的值,并且在更新num的同时也更新version,并在更新时加上对version的等值判断。假设有10个进程都读取到了num的值为1,version值为9,则这10个进程执行的更新语句都是UPDATE goods SET num=num-1,version=version+1 WHERE version=9,然而当其中一个进程执行成功之后,数据库中version的值就会变为10,剩余的9个进程都不会执行成功,这样保证了商品不会超发,num的值不会小于0,但这也导致了一个问题,那就是发出抢购请求较早的用户可能抢不到,反而被后来的请求抢到了。

$result = $this->mysqli->query("SELECT num,version FROM goods WHERE id=1 LIMIT 1");
$row = $result->fetch_assoc();
$num = intval($row['num']);
$version = intval($row['version']);
if($num > 0){
  usleep(100);
  $this->mysqli->begin_transaction();
  $this->mysqli->query("UPDATE goods SET num=num-1,version=version+1 WHERE version={$version}");
  $affected_rows = $this->mysqli->affected_rows;
  if($affected_rows == 1){
    $this->mysqli->query("INSERT INTO log(good_id) VALUES({$num})");
    $affected_rows = $this->mysqli->affected_rows;
    if($affected_rows == 1){
      $this->mysqli->commit();
      echo "success:".$num;
    }else{
      $this->mysqli->rollback();
      echo "fail1:".$num;
    }
  }else{
    $this->mysqli->rollback();
    echo "fail2:".$num;
  }
}else{
  echo "fail3:".$num;
}

where条件(原子操作)

悲观锁的方案保证了数据库中num的值在同一时间只能被一个进程读取并处理,也就是并发的读取进程到这里要排队依次执行。乐观锁的方案虽然num的值可以被多个进程同时读取到,但是更新操作中version的等值判断可以保证并发的更新操作在同一时间只能有一个更新成功。

还有一种更简单的方案,只在更新操作时加上num>0的条件限制即可。通过where条件限制的方案虽然看似和乐观锁方案类似,都能够防止超发问题的出现,但在num较大时的表现还是有很大区别的。假如此时num为10,同时有5个进程读取到了num=10,对于乐观锁的方案由于version字段的等值判断,这5个进程只会有一个更新成功,这5个进程执行完成之后num为9;对于where条件判断的方案,只要num>0都能够更新成功,这5个进程执行完成之后num为5。

$result = $this->mysqli->query("SELECT num FROM goods WHERE id=1 LIMIT 1");
$row = $result->fetch_assoc();
$num = intval($row['num']);
if($num > 0){
  usleep(100);
  $this->mysqli->begin_transaction();
  $this->mysqli->query("UPDATE goods SET num=num-1 WHERE num>0");
  $affected_rows = $this->mysqli->affected_rows;
  if($affected_rows == 1){
    $this->mysqli->query("INSERT INTO log(good_id) VALUES({$num})");
    $affected_rows = $this->mysqli->affected_rows;
    if($affected_rows == 1){
      $this->mysqli->commit();
      echo "success:".$num;
    }else{
      $this->mysqli->rollback();
      echo "fail1:".$num;
    }
  }else{
    $this->mysqli->rollback();
    echo "fail2:".$num;
  }
}else{
  echo "fail3:".$num;
}


      

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/5091.html