【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_ ...
随机推荐
- 【LabVIEW技巧】LabVIEW中的错误2
前言 通过上一个文章的介绍,我们发现LabVIEW自带的错误管理依旧比较基础,如果需要对错误进行很好的管理,则需要进一步的进行程序编写. 用于在程序设计的过程中,为了保证程序的健壮性,我们需要 1.忽 ...
- mui 选项卡与header文字同步
mui底部tab固定 头部nav可变 <!DOCTYPE html> <html> <head> <meta charset="utf-8" ...
- 16:django 有条件的视图处理(Last-Modified和ETag)&&加密签名
有条件的视图处理 上一节我们介绍了缓存来减轻服务器的负担,这里的有条件的视图处理也从一定程度上减轻了服务器的负担,在正式介绍之前,先来看两个概念:Last-Modified和ETag Last-Mod ...
- JavaScript自定义事件,动态添加属性
根据事件的不同,可用的自定义方法也不同. document.createEvent('Event'); 实现主要有4个步骤: 1.创建事件. 2.初始化事件(三个参数:事件名,是否起泡,是否取消默认触 ...
- python 用abc模块构建抽象基类Abstract Base Classes
见代码: #!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2018/08/01 16:58 from abc import ABCMet ...
- AC日记——魔法森林 洛谷 P2387
魔法森林 思路: spfa水过(正解lct); 代码: #include <bits/stdc++.h> using namespace std; #define maxn 50005 # ...
- section
@RenderSection("Header") @section Header { <div class="view"> @foreach ( ...
- 【剑指offer】面试题 10. 斐波那契数列
面试题 10. 斐波那契数列 题目一:求斐波那契数列的第n项 题目描述:求斐波拉契数列的第n项 写出一个函数,输入n,求斐波拉契(Fibonacci)数列的第n项.斐波拉契数列定义如下: C++ 实现 ...
- 丑数(LintCode)
丑数 设计一个算法,找出只含素因子3,5,7 的第 k大的数. 符合条件的数如:3,5,7,9,15...... 您在真实的面试中是否遇到过这个题? Yes 样例 如果k=4, 返回 9 挑战 要求时 ...
- Bzoj 2286 & Luogu P2495 消耗战(LCA+虚树+欧拉序)
题面 洛谷 Bzoj 题解 很容易想到$O(nk)$的树形$dp$吧,设$f[i]$表示处理完这$i$颗子树的最小花费,同时再设一个$mi[i]$表示$i$到根节点$1$路径上的距离最小值.于是有: ...