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字段实现乐观锁,实现高并发下的订单库存的并发控制,通过开启多线程同时处理模拟多个请求同时到达的情况 + 同一事务中使用多个乐观锁的情况处理的更多相关文章

  1. 高并发场景系列(一) 利用redis实现分布式事务锁,解决高并发环境下减库存

    原文:http://blog.csdn.net/heyewu4107/article/details/71009712 高并发场景系列(一) 利用redis实现分布式事务锁,解决高并发环境下减库存 问 ...

  2. 数据库事务中的隔离级别和锁+spring Transactional注解

    数据库事务中的隔离级别和锁 数据库事务在后端开发中占非常重要的地位,如何确保数据读取的正确性.安全性也是我们需要研究的问题.ACID首先总结一下数据库事务正确执行的四个要素(ACID): 原子性(At ...

  3. [转]数据库事务中的隔离级别和锁+spring Transactional注解

    数据库事务中的隔离级别和锁 数据库事务在后端开发中占非常重要的地位,如何确保数据读取的正确性.安全性也是我们需要研究的问题.ACID首先总结一下数据库事务正确执行的四个要素(ACID): 原子性(At ...

  4. MySQL 在高并发下的 订单撮合 系统使用 共享锁 与 排他锁 保证数据一致性

    作者:林冠宏 / 指尖下的幽灵 掘金:https://juejin.im/user/587f0dfe128fe100570ce2d8 博客:http://www.cnblogs.com/linguan ...

  5. MySQl中隔离级别和悲观锁乐观锁

    1.MySql的事物支持 MySQL的事务支持不是绑定在MySQL服务器本身,而是与存储引擎相关: MyISAM:不支持事务,用于只读程序提高性能 InnoDB:支持ACID事务.行级锁.并发 Ber ...

  6. 谈谈MySQL支持的事务隔离级别,以及悲观锁和乐观锁的原理和应用场景?

    在日常开发中,尤其是业务开发,少不了利用 Java 对数据库进行基本的增删改查等数据操作,这也是 Java 工程师的必备技能之一.做好数据操作,不仅仅需要对 Java 语言相关框架的掌握,更需要对各种 ...

  7. 第36讲 谈谈MySQL支持的事务隔离级别,以及悲观锁和乐观锁的原理和应用场景

    在日常开发中,尤其是业务开发,少不了利用 Java 对数据库进行基本的增删改查等数据操作,这也是 Java 工程师的必备技能之一.做好数据操作,不仅仅需要对 Java 语言相关框架的掌握,更需要对各种 ...

  8. redis事务中的WATCH命令和基于CAS的乐观锁

    转自:http://blog.sina.com.cn/s/blog_ae8441630101cgy3.html 在Redis的事务中,WATCH命令可用于提供CAS(check-and-set)功能. ...

  9. 使用数据库乐观锁解决高并发秒杀问题,以及如何模拟高并发的场景,CyclicBarrier和CountDownLatch类的用法

    数据库:mysql 数据库的乐观锁:一般通过数据表加version来实现,相对于悲观锁的话,更能省数据库性能,废话不多说,直接看代码 第一步: 建立数据库表: CREATE TABLE `skill_ ...

随机推荐

  1. tableView选中行的调用顺序/ 取消选中Cell

    UITableViewCell它有两个属性highLighted.selected.很明显一个是高亮状态, 一个是选中状态. UITableViewCell, 对应的2个方法 // 高亮状态调用的方法 ...

  2. springboot在不同环境下进行不同的配置

    原文链接:http://www.cnblogs.com/java-zhao/p/5469183.html 不同的环境设置一个配置文件,例如:dev(开发)环境下的配置设置在application-de ...

  3. Makefile系列之四 :条件判断

    一.示例 下面的例子,判断$(CC)变量是否“gcc”,如果是的话,则使用GNU函数编译目标. libs_for_gcc = -lgnu normal_libs = foo: $(objects) i ...

  4. Linux非常用命令

    查看系统版本(64位还是32位版本) uname -a 或 more /proc/version 执行结果

  5. Servlet的监听器Listener

    Servlet的监听器Listener,它是实现了javax.servlet.ServletContextListener 接口的服务器端程序,它也是 随web应用的启动而启动,只初始化一次,随web ...

  6. hdu 2147(巴什博弈+NP图)

    kiki's game Time Limit: 5000/1000 MS (Java/Others)    Memory Limit: 40000/10000 K (Java/Others)Total ...

  7. Aras Innovator 11 sp2 firefox客户端设置

    在上一篇文章<Aras Innovator 11 sp2 安装>后,服务器算是安装好了,还需要在使用的客户端进行设置才可以正常使用Aras Innovator 该篇为firefox设置,还 ...

  8. 【Nginx】初试反向代理:反向代理的原理和用途

    Nginx是一个轻量级的服务器,是一个俄罗斯的开发者开发的开源软件.Nginx具有占内存小.并发能力高的特点,底层采用epoll(Linux2.6+)和kqueue(FREEBSD)网络I/O模型,相 ...

  9. android studio安卓工作室 工具栏

    韩梦飞沙  韩亚飞  313134555@qq.com  yue31313  han_meng_fei_sha android studio安卓工作室 工具栏  都有什么 . 打开.保存全部.刷新.撤 ...

  10. windows上同时安装两个版本的mysql数据库

    一.先停止之前安装的低版本mysql服务 二.将其他电脑上安装好的mysql拷贝过来 三.拷贝过来之后,进入该文件夹,删除掉data目录,然后打开my.ini,进行修改端口号,端口号改为3307,ba ...