基于mysql乐观锁实现秒杀的示例代码
作者:yy1209357299 发布时间:2024-01-18 02:01:41
说明
如果你的项目流量非常小,完全不用担心有并发的购买请求,那么做这样一个系统意义不大。但如果你的系统要像12306那样,接受高并发访问和下单的考验,那么你就需要一套完整的流程保护措施,来保证你系统在用户流量高峰期不会被搞挂了。
进阶redis+mq实现:参考springboot + rabbitmq + redis实现秒杀
严格防止超卖
保证用户体验:高并发下,别网页打不开了,支付不成功了,购物车进不去了,地址改不了了
防止黑产:防止不怀好意的人群通过各种技术手段把你本该下发给群众的利益全收入了囊中
具体实现
1、核心
mysql乐观锁防止超卖
乐观锁是指操作数据库时(更新操作),想法很乐观,认为这次的操作不会导致冲突,在操作数据时,并不进行任何其他的特殊处理(也就是不加锁),而在进行更新后,再去判断是否有冲突了。
这里是引用通常实现是这样的:在表中的数据进行操作时(更新),先给数据表加一个版本(version)字段,每操作一次,将那条记录的版本号加1。也就是先查询出那条记录,获取出version字段,如果要对那条记录进行操作(更新),则先判断此刻version的值是否与刚刚查询出来时的version的值相等,如果相等,则说明这段期间,没有其他程序对其进行操作,则可以执行更新,将version字段的值加1;如果更新时发现此刻的version值与刚刚获取出来的version的值不相等,则说明这段期间已经有其他程序对其进行操作了,则不进行更新操作。
2、建表语句
stock商品表
-- ----------------------------
-- Table structure for stock
-- ----------------------------
DROP TABLE IF EXISTS `stock`;
CREATE TABLE `stock` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL DEFAULT '' COMMENT '名称',
`count` int(11) NOT NULL COMMENT '库存',
`sale` int(11) NOT NULL COMMENT '已售',
`version` int(11) NOT NULL COMMENT '乐观锁,版本号',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
初始化数据:
stock_order订单表
-- ----------------------------
-- Table structure for stock_order
-- ----------------------------
DROP TABLE IF EXISTS `stock_order`;
CREATE TABLE `stock_order` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`sid` int(11) NOT NULL COMMENT '库存ID',
`name` varchar(30) NOT NULL DEFAULT '' COMMENT '商品名称',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
3、业务流程
代码实现
1、pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--mysql-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
2、model
可通过逆向工程进行配置,参考idea+mybatis逆向工程
4、dao
public interface StockMapper {
Stock checkStock(Integer id);//校验库存
int updateSale(Stock stock);//扣除库存
}
public interface StockOrderMapper {
//创建订单
void createOrder(StockOrder order);
}
5、sql
商品校验和减库存
<select id="checkStock" parameterType="java.lang.Integer" resultType="com.yy.msserver.model.vo.Stock">
select * from stock where id = #{id}
</select>
<update id="updateSale" parameterType="com.yy.msserver.model.vo.Stock" >
update stock
set sale = #{sale,jdbcType=INTEGER} + 1,
version = #{version,jdbcType=INTEGER} + 1,
count = #{count,jdbcType=INTEGER} - 1
where id = #{id,jdbcType=INTEGER}
AND count > 0 AND version = #{version}
</update>
下订单
<insert id="createOrder" parameterType="com.yy.msserver.model.vo.StockOrder">
insert into stock_order (sid, name,
create_time)
values (#{sid,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR},
#{createTime,jdbcType=TIMESTAMP})
</insert>
6、service
public interface StockOrderService {
public Integer createOrder(Integer id);
}
7、实现
/**
* @author code
* @Date 2022/6/24 9:25
* Description 订单实现
* Version 1.0
*/
@Service
public class StockOrderServiceImpl implements StockOrderService {
@Autowired
private StockOrderMapper stockOrderMapper;
@Autowired
private StockMapper stockMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public Integer createOrder(Integer id) {
//校验库存
Stock stock = checkStock(id);
if(stock.getCount()>0){
System.out.println("当前库存:" + stock.getCount());
//扣库存
if(updateSale(stock) == 1){
return createOrder(stock);
}else {
return 0;
}
}
return 0;
}
//校验库存
private Stock checkStock(Integer id) {
return stockMapper.checkStock(id);
}
//扣库存
private int updateSale(Stock stock){
return stockMapper.updateSale(stock);
}
//下订单
private Integer createOrder(Stock stock){
StockOrder order = new StockOrder();
order.setSid(stock.getId());
order.setCreateTime(new Date());
order.setName(stock.getName());
stockOrderMapper.createOrder(order);
return order.getId();
}
}
8、测试
模拟100人参与活动
@SpringBootTest
class MsServerApplicationTests {
@Autowired
private StockOrderService stockOrderService;
@Test
void contextLoads() throws InterruptedException {
// 库存初始化为10,这里通过CountDownLatch和线程池模拟100个并发
int threadTotal = 100;
ExecutorService executorService = Executors.newCachedThreadPool();
final CountDownLatch countDownLatch = new CountDownLatch(threadTotal);
for (int i = 0; i < threadTotal ; i++) {
int uid = i;
executorService.execute(() -> {
try {
stockOrderService.createOrder(1);
} catch (Exception e) {
e.printStackTrace();
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
}
}
9、结果
商品表
订单表
来源:https://blog.csdn.net/yy1209357299/article/details/125448728


猜你喜欢
- filter(function or None, sequence),其中sequence 可以是list ,tuple,string。这个
- MSDN上看了一下说是sql server 2005不支持在分布式事务处理中存在指向本地的链接服务器(环回链接服务器)个人尝试了下是由于在双
- 在我们的生活中,需要接触大量的带有机械按键的物品。当你用手指按下按键的时候。都会或强或弱的感受一股(嗯,也有可能是一丝丝)反作用力传递到你手
- 协程协程是一种用户态的轻量级线程,又称微线程。协程拥有自己的寄存器上下文和栈,调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,
- --为空的值text ntext select * from lf_newsNg_utf where datalength(newsCont
- 最近在看廖老师的python教程,在看到关于文件的操作时,廖老师的其中一段关于查找电脑里的python文件,突然想把之前写的python代码
- 从官方文档知道linux上面编译安装的mysql默认的连接为100个,这样对于网站的需求来说是远远不够的。 mysql官方告诉我们需要修改m
- import java.io.BufferedReader;import java.io.File;import java.io.FileI
- 1.数组的索引我用的是iloc函数。导入数据是data,索引data.iloc[i,j],i代表行,j代表列。如果要索引i行之后的所有行元素
- 前言随着Python 3.8的发布,赋值表达式运算符(也称为海象运算符)也发布了。运算符使值的赋值可以传递到表达式中。这通常会使语句数减少一
- 由于日期存在不同位数的月份或天数,出现参差不齐,既不美观也在日期比较时不好操作。如使用本涵数就会排列整齐:'================
- 啥也不说了,大家还是直接看图吧!补充知识:python http request header主要内容http request 请求头主要包
- 本文实例讲述了golang实现http服务器处理静态文件的方法。分享给大家供大家参考,具体如下:新版本更精简:package mainimp
- 初学python,看来零零碎碎的格式化文本的方法,总结一下python中格式化文本的方法。使用不当的地欢迎指出谢谢。1、首先看使用%格式化文
- 偶然遇到的问题,记录如下:通常我们在push项目时,会有些配置文件或本地文件不想上传到服务器上这时候我们会通过设置.gitignore&nb
- 我用的centos6,mysql让我整出了各种问题,我想重装一个全新的mysql,yum remove mysql-server mysql
- 在腾讯云上面搭建的mysql使用开发的电脑上navicat进行访问时总是特别的慢,原来是Mysql会对请求的地址进行域名解析,开发的电脑并没
- 网上找了半天 不是dataframe转化成array的就是array转化dataframe,所以这里给汇总一下,相互转换的python代如下
- 本文实例为大家分享了selenium+python京东自动登录及秒杀的代码,供大家参考,具体内容如下运行环境:python 2.7pytho
- 介绍在本文中,云朵君将和大家一起了解装饰器的工作原理,如何将我们之前定义的定时器类 Timer 扩展为装饰器,以及如何简化计时功能。最后对