【mysql】mysql增加version字段实现乐观锁,实现高并发下的订单库存的并发控制,通过开启多线程同时处理模拟多个请求同时到达的情况 + 同一事务中使用多个乐观锁的情况处理
mysql增加version字段实现乐观锁,实现高并发下的订单库存的并发控制,通过开启多线程同时处理模拟多个请求同时到达的情况
=============================================================
完整的代码请到GIthub查看:https://github.com/AngelSXD/swapping
多个线程处理完后再做事情:https://www.cnblogs.com/sxdcgaq8080/p/9456006.html
=============================================================
先说说同一个事务中使用一个乐观锁的情况:
核心功能点:
1.先做查询 【查询时候把version带出来】
<select id="findByUid" parameterType="String" resultType="com.sxd.swapping.domain.GoodsStock">
select
uid uid,
version version,
sale_num saleNum,
stock stock
from
goods_stock
where
uid = #{uid} </select>
2.再做更新【更新的时候判断version是不是查出来时候的version,如果是,则更新,更新时顺便version+1即可。否则不更新】
<update id="updateStock" parameterType="com.sxd.swapping.domain.GoodsStock">
update
goods_stock
set
<if test="stock != -1">
stock = stock - #{buyNum},
</if>
sale_num = sale_num + #{buyNum},
version = version + 1
where
uid = #{uid}
and
version = #{version}
</update>
=============================================================
1.实体对应数据表
/**
* 低配版本的 商品库存表
*/
@Entity
@Table
@Getter
@Setter
public class GoodsStock extends BaseBean { private String goodsName;//商品名称 private String goodsPrice;//商品价格 private Long buyNum;//购买数量 private Long saleNum;//销售量 private Long stock;//商品库存 库存为-1 代表无限量库存 private Integer version;//版本号 @Transient
private Integer threadCount;//模拟并发访问的线程数量 实际业务中不用这个字段 仅用作本次测试接口使用 }

2.mybatis的mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sxd.swapping.dao.mybatis.GoodsStockMapper"> <update id="updateStock" parameterType="com.sxd.swapping.domain.GoodsStock">
update
goods_stock
set
<if test="stock != -1">
stock = stock - #{buyNum},
</if>
sale_num = sale_num + #{buyNum},
version = version + 1
where
uid = #{uid}
and
version = #{version}
</update> <select id="findByUid" parameterType="String" resultType="com.sxd.swapping.domain.GoodsStock">
select
uid uid,
version version,
sale_num saleNum,
stock stock
from
goods_stock
where
uid = #{uid} </select> </mapper>
mybatis的mapper.java
package com.sxd.swapping.dao.mybatis; import com.sxd.swapping.domain.GoodsStock;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param; @Mapper
public interface GoodsStockMapper { int updateStock(GoodsStock goodsStock); GoodsStock findByUid(@Param("uid") String uid);
}
3.serviceImpl层代码
@Autowired
GoodsStockMapper mapper; /**
* 数据库加 version 版本号
*
* 实现 数据库乐观锁
*
* 实现高并发下库存的并发控制机制
*
* 要保证事务一致性,要么都使用mybatis 要么都使用jpa
* @param map
* @param entity
* @param threadNum
* @return
*/
@Override
@Transactional
public void updateStock(Map<Integer,String> map, GoodsStock entity, Integer threadNum) { String uid = entity.getUid();
Long buyNum = entity.getBuyNum();
String msg = "";
//判断库存是否足够
GoodsStock old = mapper.findByUid(uid);
Long stock = old.getStock();
System.out.println("线程"+threadNum+"---------->正在工作");
if (stock >= buyNum){
old.setBuyNum(buyNum);
if (mapper.updateStock(old) > 0 ){
msg = "库存扣除成功,剩余库存数量:";
}else {
msg = "库存扣除失败,剩余库存数量:";
}
Long nowStock = mapper.findByUid(uid).getStock();
msg +=nowStock;
}else {
msg = "库存不足,剩余库存数量:"+stock;
}
map.put(threadNum,msg);
}
4.controller层代码:
/**
* uid代表 同一时间 大家都来买这一件东西
* threadCount代表 同时会有多少人在操作
* buyNum代表 同一个人的一次购买量
* @param entity
* @return
*/
@RequestMapping(value = "/concurrentStock",method = RequestMethod.POST)
public UniVerResponse<Map<Integer,String>> concurrentStock(@RequestBody GoodsStock entity){
UniVerResponse.checkField(entity,"uid","threadCount","buyNum");
UniVerResponse<Map<Integer,String>> res = new UniVerResponse<>(); String uid = entity.getUid(); GoodsStock old = service.findByUid(uid);
if (old != null){
//设置一个线程安全的Map记录各个线程是否成功执行
Map<Integer,String> map = new ConcurrentHashMap<Integer, String>(); Integer threadCount = entity.getThreadCount();
//所有线程阻塞,然后统一开始
CountDownLatch begin = new CountDownLatch(1); //主线程阻塞,直到所有分线程执行完毕
CountDownLatch end = new CountDownLatch(threadCount); //开始多线程
begin.countDown();
for (Integer i = 0; i < threadCount; i++) {
Runnable runnable = buyGoods(map,entity,i,begin,end);
new Thread(runnable).start();
} //多个线程都执行结束
try {
end.await();
res.beTrue(map);
} catch (InterruptedException e) {
e.printStackTrace();
res.beFalse("多线程执行失败",UniVerResponse.ERROR_BUSINESS,null);
}
}else {
res.beFalse("商品不存在",UniVerResponse.ERROR_BUSINESS,null);
}
return res;
} //多线程的方法
public Runnable buyGoods(Map<Integer,String> map, GoodsStock entity, Integer threadNum,CountDownLatch begin,CountDownLatch end){
Runnable runnable = new Runnable() {
@Override
public void run() { try {
System.out.println("线程"+threadNum+":--------------------->开始工作");
begin.await(); service.updateStock(map,entity,threadNum); end.countDown();
System.out.println("线程"+threadNum+":--------------------->结束工作");
} catch (InterruptedException e) {
e.printStackTrace();
} }
};
return runnable;
}
5.发送请求

第一次请求:
下图所示,仅有线程编号为3的 线程 购买成功,其他都购买失败。


第二次请求:


第三次请求:


第四次请求:


最后一次请求:


二.再说说在同一个事务中使用多个乐观锁的情况
===============================================================================================
下面仅写一段代码举个例子即可:
即 第一步操作,第二步 都会使用乐观锁
如果执行失败有两种情况:
1.数据库连接断开,sql真正的执行出错
2.sql成功执行,但是其实update执行失败,因为version对应不起来
所以需要注意的是 如果使用乐观锁执行失败[失败情况2],那么需要自己手动去抛出异常,去保证事务的一致性!!!
因为失败情况1自己会抛出RuntimeException
因为下面示例代码中的第一步操作如果失败了会直接返回 所以并没有去抛异常
/**
* 进行兑换
*
* 1.减少会员积分总数[加乐观锁]
*
* 2.减少商品库存 增加商品销量[加乐观锁]
*
* 3.新增兑换记录
*
*
* @param entity
* @return
*/
@Override
@Transactional
public boolean insert(ExchangeOrder entity,String integralUid,Integer buyIntegral) { boolean isSuccess = false; //1.减少会员积分
IntegralDetail integralDetail = integralDetailMapper.findByIntegralId(integralUid);
integralDetail.setIntegralValue(buyIntegral);//sql 做减操作
isSuccess = (integralDetailMapper.deductIntegral(integralDetail) > 0); if (isSuccess){
//2.减少商品库存 增加商品销量
IntegralGoods integralGoods = integralGoodsMapper.findByUid(entity.getIntegralGoodsId());
//无限库存不做修改
if (integralGoods.getStock() != -1) {
integralGoods.setStock(entity.getBuyNum());
}
//增加销量
integralGoods.setSaleNum(entity.getBuyNum());
integralGoods.initUpdateDataMen();
isSuccess = (integralGoodsMapper.updateStock(integralGoods) > 0); if (isSuccess){
//3.新增兑换记录
mapper.insert(entity);
}else{
throw new RunException("销量增加失败,请稍后再试");
}
} return isSuccess;
}
【mysql】mysql增加version字段实现乐观锁,实现高并发下的订单库存的并发控制,通过开启多线程同时处理模拟多个请求同时到达的情况 + 同一事务中使用多个乐观锁的情况处理的更多相关文章
- 高并发场景系列(一) 利用redis实现分布式事务锁,解决高并发环境下减库存
原文:http://blog.csdn.net/heyewu4107/article/details/71009712 高并发场景系列(一) 利用redis实现分布式事务锁,解决高并发环境下减库存 问 ...
- 数据库事务中的隔离级别和锁+spring Transactional注解
数据库事务中的隔离级别和锁 数据库事务在后端开发中占非常重要的地位,如何确保数据读取的正确性.安全性也是我们需要研究的问题.ACID首先总结一下数据库事务正确执行的四个要素(ACID): 原子性(At ...
- [转]数据库事务中的隔离级别和锁+spring Transactional注解
数据库事务中的隔离级别和锁 数据库事务在后端开发中占非常重要的地位,如何确保数据读取的正确性.安全性也是我们需要研究的问题.ACID首先总结一下数据库事务正确执行的四个要素(ACID): 原子性(At ...
- MySQL 在高并发下的 订单撮合 系统使用 共享锁 与 排他锁 保证数据一致性
作者:林冠宏 / 指尖下的幽灵 掘金:https://juejin.im/user/587f0dfe128fe100570ce2d8 博客:http://www.cnblogs.com/linguan ...
- MySQl中隔离级别和悲观锁乐观锁
1.MySql的事物支持 MySQL的事务支持不是绑定在MySQL服务器本身,而是与存储引擎相关: MyISAM:不支持事务,用于只读程序提高性能 InnoDB:支持ACID事务.行级锁.并发 Ber ...
- 谈谈MySQL支持的事务隔离级别,以及悲观锁和乐观锁的原理和应用场景?
在日常开发中,尤其是业务开发,少不了利用 Java 对数据库进行基本的增删改查等数据操作,这也是 Java 工程师的必备技能之一.做好数据操作,不仅仅需要对 Java 语言相关框架的掌握,更需要对各种 ...
- 第36讲 谈谈MySQL支持的事务隔离级别,以及悲观锁和乐观锁的原理和应用场景
在日常开发中,尤其是业务开发,少不了利用 Java 对数据库进行基本的增删改查等数据操作,这也是 Java 工程师的必备技能之一.做好数据操作,不仅仅需要对 Java 语言相关框架的掌握,更需要对各种 ...
- redis事务中的WATCH命令和基于CAS的乐观锁
转自:http://blog.sina.com.cn/s/blog_ae8441630101cgy3.html 在Redis的事务中,WATCH命令可用于提供CAS(check-and-set)功能. ...
- 使用数据库乐观锁解决高并发秒杀问题,以及如何模拟高并发的场景,CyclicBarrier和CountDownLatch类的用法
数据库:mysql 数据库的乐观锁:一般通过数据表加version来实现,相对于悲观锁的话,更能省数据库性能,废话不多说,直接看代码 第一步: 建立数据库表: CREATE TABLE `skill_ ...
随机推荐
- mybatis模糊查询sql
今天下午做的一个功能,要用到模糊查询,字段是description,刚开始我的写法用的是sql中的模糊查询语句, 但是这个有问题,只有将字段的全部值传入其中,才能查询,所以不是迷糊查询. 后来经过搜索 ...
- shell常见操作整理(更新)
查看文件第20到30行的内容 法一:[root@oldboy ~]# seq 100 > ett.txt [root@oldboy ~]# head -30 ett.txt | tail -11 ...
- iframe自适应高度的方法
不带边框的iframe因为能和网页无缝的结合从而不刷新新页面的情况下实现更新页面部分的数据成为可能,可是iframe却不像层那样可以收缩自如,iframe高度需要动态的调整需要JS来配合使用,只能通过 ...
- 17:django Email
我自己在看这一节之前自己先实现了一下,django-admin.py startproject testEmail新建一个新项目,urls.py把urlpatterns的第一行取消注释,改成url(r ...
- Dubbo简单DEMO以及重要配置项
DEMO pom.xml 消费方和服务提供方一致 <properties> <spring.version>4.0.6.RELEASE</spring.version&g ...
- Python 面向对象的三大特性
面向对象的三大特性:继承,封装,多态 什么时候用封装: 同一种功能的时候, 譬如:把一部分数据或方法,封装到同一个类的中 PS:在构造方法中,原始数据中....
- zookeeper编程入门系列之zookeeper实现分布式进程监控和分布式共享锁(图文详解)
本博文的主要内容有 一.zookeeper编程入门系列之利用zookeeper的临时节点的特性来监控程序是否还在运行 二.zookeeper编程入门系列之zookeeper实现分布式进程监控 三. ...
- MVC - 12.HtmlHelper
1.动态生成URL @Url.Action("Index3","Stu3") @Url.RouteUrl("Default2", new { ...
- 175. Combine Two Tables
Table: Person +-------------+---------+ | Column Name | Type | +-------------+---------+ | PersonId ...
- sql server 完整备份、差异备份、事务日志备份
一. 理解: 完整备份为基础, 完整备份可以实物回滚还原,但是由于完整备份文件过大,对硬盘空间比较浪费这是就需要差异备份 或者 事务日志备份. 差异备份还原时,只能还原到备份的那个点, 日志备份还原时 ...