抢购功能:在极短的时间内有大量的并发请求,出现因为并发所引起的问题。
实例:
一个商品50件,一个用户一次购买3件,100个用户同时购买。
设计:
mysql数据库
建表语句:
CREATE TABLE `product` (
`id` int(11) NOT NULL,
`product_id` int(11) DEFAULT NULL,
`stock` int(11) DEFAULT NULL COMMENT '库存',
`version` int(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `product_info` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`product_num` int(11) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5739 DEFAULT CHARSET=utf8;
前端模拟并发请求:
function aaa(){
for (var i = 1; i<=100;i++){
$.post("http://localhost:8081/xxx/xxx",function (result) {
});
}
}
并发量测试
测试两组数据:
产品数为50,每个用户买3,100用户同时抢购(在系统并发内),系统并发为21,也就是说最多处理63个产品 产品数为100,每个用户买3,100用户同时抢购(在系统并发外)
并发量和电脑cpu和内存以及jvm等有关,未使用连接池,目前无法得知和mysql的连接数是否有关。
第一种情况:不用锁
@Transactional
public void common(int num){
Product product =productDao.selectOne(new QueryWrapper().eq("id",1));
if (product.getStock()>=num){
product.setStock(product.getStock()-num);
ProductInfo productInfo=new ProductInfo();
productInfo.setProductNum(num);
productInfo.setCreateTime(LocalDateTime.now());
productDao.updateById(product);
productInfoDao.insert(productInfo);
}
}
结果:
50:产品表剩余2,用户购买表77个订单,超发现象。 100:产品表剩余37,用户购买表100个订单,出现线程不安全问题。
第二种情况:悲观锁
悲观锁直接去操作sql语句,在最后面加上 for update即可。
@Transactional
public void beiguan(int num){
Product product =productDao.selectOne(new QueryWrapper().eq("id",1).last("for update"));
if (product.getStock()>=num){
product.setStock(product.getStock()-num);
ProductInfo productInfo=new ProductInfo();
productInfo.setProductNum(num);
productInfo.setCreateTime(LocalDateTime.now());
productDao.updateById(product);
productInfoDao.insert(productInfo);
}
}
结果:
50:产品表剩余2,用户购买表16个订单,发放正常,会影响效率。 100:产品表剩余1,用户购买表33个订单,发放正常,会影响效率。
第三种:乐观锁
@Transactional
public void leguan(int num){
for (int i=0;i<3;i++){
Product product = productDao.getStock();
if (product.getStock()>=num){
int flag =productDao.updStock(product.getVersion());
if (flag==0){
continue;
}
ProductInfo productInfo=new ProductInfo();
productInfo.setProductNum(num);
productInfo.setCreateTime(LocalDateTime.now());
productInfoDao.insert(productInfo);
return;
}
}
}
结果:
50:产品表剩余2,用户购买表16个订单,发放正常。 100:产品表剩余40,用户购买表20个订单,发放不正常,数据正常,存在请求失败情况。
总结:
1.select * from a for update 会加上悲观锁,只能被一个资源持有,做什么都上锁。效率低,有时候称为排它锁,独占锁
2.乐观锁时在sql语句加上一个只增不减得版本号,判断版本号是否一致,一致则修改,不一致则是其他线程修改了,取消这次操作。
3.乐观锁还可以引入重入机制,一旦失败就重新做一次。称为可重入锁。
4.可重入锁避免多次给予数据库压力,限制时间或者次数,超过时间限制就不再重入。