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. Cent os FTP配置

    原文:http://www.aicoffees.com/itshare/412261137.html

  2. 在ie10中如何禁用输入框中的小眼睛 与 叉叉 删除按钮

    修改本地组策略,禁用密码输入框中的密码显示: 切换成IE兼容模式:(此方法仅在Windows 7下有效,Windows 8无效) 那是系统自己支持的,有时候很方便,有时候会影响文本框里的文本,提供一个 ...

  3. [PAT] 1147 Heaps(30 分)

    1147 Heaps(30 分) In computer science, a heap is a specialized tree-based data structure that satisfi ...

  4. Trapping Rain Water——经典的双边扫描问题

    Given n non-negative integers representing an elevation map where the width of each bar is 1, comput ...

  5. Java学习笔记(十)——xml

    [前面的话] 学习过程中还是效率不够高,老是容易注意力不集中,着急啊.不能在这样了,要好好学习,好好努力. 学习过程中的小知识点总结,基础知识,选择阅读. [xml定义] 定义:可扩展标记语言(英语: ...

  6. mp4文件格式解析(转载)

    mp4文件格式解析 原作:http://blog.sina.com.cn/s/blog_48f93b530100jz4b.html 目前MP4的概念被炒得很火,也很乱.最开始MP4指的是音频(MP3的 ...

  7. OOX之间的关系

    OOA,OOD,OOP三者关系OOA的分析结果可以作为OOD的需求模型OOD的设计结果作为OOP的指导蓝图OOP负责最终实现目标系统

  8. EOJ Monthly 2018.8 D. Delivery Service-树上差分(边权/边覆盖)(边权转点权)(模板题)

    D. Delivery Service 单测试点时限: 2.5 秒 内存限制: 512 MB EOJ Delivery Service Company handles a massive amount ...

  9. Java中面向对象的分拣存储

    Student.java package yzhou.map; /** * 学生类 * * * @author 洋 * */ public class Student { private String ...

  10. Java基础知识(二)

    一.基本概念 1. Java程序初始化的顺序是怎么样的 2. Java和C++的区别 3. 反射 先看一个知乎回答 什么是反射 主要用途 获得Class对象 4. 注解 什么是注解 为什么要用注解 基 ...