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. sicily 1046. Plane Spotting

    1046. Plane Spotting Time Limit: 1sec    Memory Limit:32MB  Description Craig is fond of planes. Mak ...

  2. win10安装提示“我们无法创建新的分区”

    今日于笔记本安装win10时突然出现提示:我们无法创建新的分区.网上搜了不少建议,尝试了都无果. 由于我的笔记本是固态硬盘与机械硬盘混合,所以情况可能更加特殊. 最后成功的方法是: 1. 先将Win1 ...

  3. caffe Python API 之InnerProduct

    net.fc3 = caffe.layers.InnerProduct(net.pool1, num_output=1024, weight_filler=dict(type='xavier'), b ...

  4. [New learn]讲解Objective-c的block知识-实践

    1.简介 在之前的文章[New learn]讲解Objective-c的block知识中介绍了block的相关知识.本章中我们将以一个实际例子来简单介绍一下block如何代替代理. 2.原有通过代理实 ...

  5. 虚拟机 VMware Workstation12 安装Ubuntu系统

    Ubuntu 系统是一款优秀的.基于GNU/Linux 的平台的桌面系统. 当然,目前为止很多应用程序还完全不能允许运行在 Ubuntu 系统上,而且 Ubuntu 的界面.使用方法会让大部分Wind ...

  6. Leetcode 之Balanced Binary Tree(49)

    用递归的方式来做,左右两棵子树的高度差不超过1.分成两部分,一部分递归得到树的高度,一部分递归检查左右子树是否是平衡二叉树. int getHeight(TreeNode *root) { ; ; } ...

  7. Highcharts创建一个简单的柱状图

    新建一个html文件,将highcharts引入到你的页面后,通过两个步骤我们就可以创建一个简单的图表了. 1.创建div容器 在页面的 body部分创建一个div,并指定div 的 id,高度和宽度 ...

  8. mysql部署后无法远程连接的原因(错误代码10061),服务监听127.0.0.1和0.0.0.0的区别

    在Ubuntu上部署mysql服务并添加了一个非root用户后,发现无法远程连接, Navicat连接mysql出现2003——can't connect to mysql server on loc ...

  9. 【转】Jmeter-----函数引用和函数重定向

    详见内文

  10. Java学习笔记(十三)——通过Netbeans开发环境生成oracle数据库中表的对应hibernate映射文件

    [前面的话] 身体慢慢已经快好了,感觉真好,哈哈. 这篇文章要通过Hibernate对数据库进行操作,而Netbeans可以直接通过数据库逆向生成对应的映射文件.基础文章,选择性阅读. [步骤] 1. ...