是现在很常见的应用场景。主要解决两个问题:1、高并发对数据库的压力。2、如何应对竞争解决正确的减库存(“超卖”问题)对于第一个问题,很容易想到使用缓存来处理抢购,避免直接操作数据库,比如使用Redis。重点是第二题的常规写法:查询对应商品的库存是否大于0,然后进行生成订单等操作,但是在判断库存是否大于0时,有高并发下会出问题,导致inventory出现负数0){//高并发会导致超卖$order_sn=build_order_no();//构建订单$sql="insertintoih_order(order_sn,user_id,goods_id,sku_id,price)values('$order_sn','$user_id','$goods_id','$sku_id','$price')";$order_rs=mysql_query($sql,$conn);//减少库存$sql="updateih_storesetnumber=number-{$number}wheresku_id='$sku_id'";$store_rs=mysql_query($sql,$conn);if(mysql_affected_rows()){insertLog('减库存成功');}else{insertLog('减库存失败');}}else{insertLog('库存不足');}?>优化方案一:设置库存字段编号字段为无符号,当库存为0时,因为字段不能为负,所以会返回false//库存减少$sql="updateih_storesetnumber=number-{$number}wheresku_id='$sku_id'andnumber>0";$store_rs=mysql_query($sql,$conn);if(mysql_affected_rows()){insertLog('减库存成功');}优化方案二:使用MySQL事务,锁定操作行0){//生成订单$order_sn=build_order_no();$sql="插入ih_order(order_sn,user_id,goods_id,sku_id,price)values('$order_sn','$user_id','$goods_id','$sku_id','$price')";$order_rs=mysql_query($sql,$conn);//减少库存$sql="updateih_storesetnumber=number-{$number}wheresku_id='$sku_id'";$store_rs=mysql_query($sql,$conn);if(mysql_affected_rows()){insertLog('减库存成功');mysql_query("COMMIT");//事务提交并解锁}else{insertLog('减库存失败');}}else{insertLog('库存不足');mysql_query("ROLLBACK");}?>优化3:使用非阻塞文件独占锁0){//库存是否大于0//模拟下单操作$order_sn=build_order_no();$sql="插入tintoih_order(order_sn,user_id,goods_id,sku_id,price)值('$order_sn','$user_id','$goods_id','$sku_id','$price')";$order_rs=mysql_query($sql,$conn);//减少库存$sql="updateih_storesetnumber=number-{$number}wheresku_id='$sku_id'";$store_rs=mysql_query($sql,$conn);if(mysql_affected_rows()){insertLog('减库存成功');flock($fp,LOCK_UN);//释放锁}else{insertLog('减库存失败');}}else{insertLog('库存不足');}fclose($fp);优化方案4:使用redis队列,因为pop操作是原子的,即使很多用户同时到达,也是顺序执行的,推荐使用(mysql高并发下事务性能下降明显,以及文件锁方法也是)先把商品库存如queueconnect('127.0.0.1',6379);$res=$redis->llen('goods_store');echo$res;$count=$store-$res;for($i=0;$i<$count;$i++){$redis->lpush('goods_store',1);}echo$redis->llen('goods_store');?>购买,描述逻辑connect('127.0.0.1',6379);$count=$redis->lpop('goods_store');if(!$count){insertLog('error:nostoreredis');return;}//构建订单$order_sn=build_order_no();$sql="insertintoih_order(order_sn,user_id,goods_id,sku_id,price)values('$order_sn','$user_id','$goods_id','$sku_id','$price')";$order_rs=mysql_query($sql,$conn);//减少库存$sql="updateih_storesetnumber=number-{$number}wheresku_id='$sku_id'";$store_rs=mysql_query($sql,$conn);if(mysql_affected_rows()){insertLog('减库存成功');}else{insertLog('减库存失败');}模拟5000高并发测试webbench-c5000-t60http://192.168.1.198/big/index.phpab-r-n6000-c5000http://192.168.1.198/big/index.php以上只是高并发下抢购的简单模拟。真实的场景要比这复杂得多。很多地方需要注意,比如把抢购页面做成静态的,通过ajax或者上面调用接口会导致用户抢多一个,思路:需要一个排队队列,抢购结果队列,存货队列高并发,先把用户放入队列,用一个线程循环从队列中处理一个用户,判断这个用户是否已经在抢购结果队列中,如果是,如果已经被抢购,否则已经未被抢购,库存减1,写入数据库,用户进入结果队列。测试数据表----数据库:`big`----------------------------------------------------------表结构`ih_goods`--如果不存在则创建表`ih_goods`(`goods_id`int(10)unsignedNOTNULLAUTO_INCREMENT,`cat_id`int(11)NOTNULL,`goods_name`varchar(255)NOTNULL,PRIMARYKEY(`goods_id`))ENGINE=MyISAMDEFAULTCHARSET=utf8AUTO_INCREMENT=2;----dump数据在表`ih_goods`中--INSERTINTO`ih_goods`(`goods_id`,`cat_id`,`goods_name`)VALUES(1,0,'小米手机');-----------------------------------------------------------表结构`ih_log`--CREATETABLEIFNOTEXISTS`ih_log`(`id`int(11)NOTNULLAUTO_INCREMENT,`event`varchar(255)NOTNULL,`type`tinyint(4)NOTNULLDEFAULT'0',`addtime`timestampNOTNULLDEFAULTCURRENT_TIMESTAMP,PRIMARYKEY(`id`))ENGINE=MyISAMDEFAULTCHARSET=utf8AUTO_INCREMENT=1;----dump表`ih_log`中的数据--------------------------------------------------------------表结构`ih_order`--如果不存在则创建表`ih_order`(`id`int(11)NOTNULLAUTO_INCREMENT,`order_sn`char(32)NOTNULL,`user_id`int(11)NOTNULL,`status`int(11)NOTNULLDEFAULT'0',`goods_id`int(11)NOTNULLDEFAULT'0',`sku_id`int(11)NOTNULLDEFAULT'0',`price`floatNOTNULL,`addtime`timestampNOTNULLDEFAULTCURRENT_TIMESTAMP,PRIMARYKEY(`id`))ENGINE=InnoDBDEFAULTCHARSET=utf8COMMENT='订单表'AUTO_INCREMENT=1;----转存表中的数据`ih_order`------------------------------------------------------------表的结构`ih_store`--CREATETABLEIFNOTEXISTS`ih_store`(`id`int(11)NOTNULLAUTO_INCREMENT,`goods_id`int(11)NOTNULL,`sku_id`int(10)unsignedNOTNULLDEFAULT'0',`number`int(10)NOTNULLDEFAULT'0',`freez`int(11)NOTNULLDEFAULT'0'COMMENT'虚拟库',PRIMARYKEY(`id`))ENGINE=InnoDBDEFAULTCHARSET=utf8COMMENT='库'AUTO_INCREMENT=2;----转存表中的数据`ih_store`--INSERTINTO`ih_store`(`id`,`goods_id`,`sku_id`,`number`,`freez`)VALUES(1,1,11,500,0);
