php+mysql处理产品秒杀库存超卖[高并发]
并发场景
常见的就是秒杀产品啦,某一个产品有固定的数量比如10个,今天10:00点要搞秒杀,可能有100+的人在准备着抢,
mysql数据库如下
一般的php代码处理如下
//取产品信息 $info = $db->table('Goods')->get(1); //这里查出产品信息后可能还要处理一些其它的请求,直接延迟一秒处理啦 sleep(1); //如果有库存就减库存 if ($info['stock'] > 0) { $db->table('Goods')->where(['goods_id' => 1])->setDec('stock'); }
这种情况就可能出现把库存减成负值的情况,如下模拟100个用户来请求
数据库中的库存如图
并且每次的值还不一样
使用事务来处理
使用事务的时候会自动加共享锁和排他锁来处理并发时数据冲突的情况,修改后的代码如下
try { // 开启事务 $db->beginTransaction(); //取产品信息 $info = $db->table('Goods')->get(1); //这里查出产品信息后可能还要处理一些其它的请求,直接延迟一秒处理啦 sleep(1); //如果有库存就减库存 if ($info['stock'] > 0) { $db->table('Goods')->where(['goods_id' => 1])->setDec('stock'); } else { //回滚操作 throw new Exception('库存不足'); } } catch (Exception $e) { //回滚操作 $db->rollback(); } //提交事务 $db->commit();
情况并没有好转如下图
这里我们就要理解下共享锁和排他锁的原理啦,摘自网络上的解释
mysql锁机制分为表级锁和行级锁,本文就和大家分享一下我对mysql中行级锁中的共享锁与排他锁进行分享交流。
共享锁又称为读锁,简称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。
排他锁又称为写锁,简称X锁,顾名思义,排他锁就是不能与其他所并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。
情况很明朗啦,因为第一行读产品信息时使用的是共享锁,共享锁是可以被多个用户同时使用的,并发时多个用户取到的是同样的库存,都满足情况,于是就进行一些逻辑处理最后减掉库存,导致库存成负值,所以我们要换个思路解决问题,一个用户进来后直接减掉库存,因为减库存用的是排他锁,只能一个一个来,就不会出现上面的那个情况啦如下
try { // 开启事务 $db->beginTransaction(); //先把库存减1 $db->table('Goods')->where(['goods_id' => 1])->setDec('stock'); //取产品信息 $info = $db->table('Goods')->get(1); //这里查出产品信息后可能还要处理一些其它的请求,直接延迟一秒处理啦 sleep(1); //如果有库存就减库存 if ($info['stock'] < 0) { //回滚操作 throw new Exception('库存不足'); } } catch (Exception $e) { //回滚操作 $db->rollback(); } //提交事务 $db->commit();
另外也可以改成下面的处理方法,事务中只要有对数据库有修改都会使用排他锁,所以一开始直接从符合条件的记录中减去,成功就继续失败则回滚操作如下:
try { // 开启事务 $db->beginTransaction(); $result = $db->table('Goods')->where(['goods_id' => 1, 'stock[>]' => 0])->setDec('stock'); //这里查出产品信息后可能还要处理一些其它的请求,直接延迟一秒处理啦 throw new Exception('库存不足'); sleep(1); } catch (Exception $e) { //回滚操作 $db->rollback(); } //提交事务 $db->commit();
后记:使用上面的方法处理后就可以避免库存为负值的情况啦,但是实际使用中会遇到性能问题,你会发现加事务锁后每次请求的处理时间都变长啦,如果要求响应速度快的话,这个方法就不适合啦,得配合加缓存来处理,这里就不再多说啦以后再写