Mysql如何利用乐观锁解决高并发问题

article/2025/10/7 1:55:22

Mysql如何利用乐观锁解决高并发问题

msql

  • Mysql如何利用乐观锁解决高并发问题
  • 前言
  • 一、案例说明:
  • 二、乐观锁:
    • 1.介绍:
      • 使用版本号实现乐观锁
    • 2.代码实现
  • 总结


前言

例如:在这之前已经许久未写博客了,最近突发奇想还是决定把这个捡起来,本篇文章将讲述mysql乐观锁解决高并发的问题。


一、案例说明:

银行两操作员同时操作同一账户。
比如A、B操作员同时读取一余额为1000元的账户,A操作员为该账户增加100元B操作员同时为该账户扣除50元A先提交B后提交最后实际账户余额为1000-50=950元,但本该为1000+100-50=1050。这就是典型的并发问题。

乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本(Version)记录机制实现何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。

读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。

对于上面修改用户帐户信息的例子而言,假设数据库中帐户信息表中有一个version字段,当前值为1;而当前帐户余额字段(balance)为1000元。假设操作员A先更新完,操作员B后更新。
a、操作员A此时将其读出(version=1),并从其帐户余额中增加100(1000+100=1100)。
b、在操作员A操作的过程中,操作员B也读入此用户信息(version=1),并从其帐户余额中扣除50(1000-50=950)。
c、操作员A完成了修改工作,将数据版本号加一(version=2),连同帐户增加后余额(balance=1100),提交至数据库更新,此时由于提交数据版本大于数据库记录当前版本,数据被更新,数据库记录version更新为2。
d、操作员B完成了操作,也将版本号加一(version=2)试图向数据库提交数据(balance=950),但此时比对数据库记录版本时发现,操作员B提交的数据版本号为2,数据库记录当前版本也为2,不满足 “提交版本必须大于记录当前版本才能执行更新 “的乐观锁策略,因此,操作员B的提交被驳回。
这样,就避免了操作员B用基于version=1的旧数据修改的结果覆盖操作员A的操作结果的可能。

乐观锁介绍:

二、乐观锁:

1.介绍:

代码如下(示例):乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。

使用版本号实现乐观锁

使用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。

在这里插入图片描述

2.代码实现

代码如下(示例):
商品goods表中有一个字段status,status为1代表商品未被下单,status为2代表商品已经被下单,那么我们对某个商品下单时必须确保该商品status为1。假设商品的id为1。

下单操作包括3步骤:

1.查询出商品信息

select (status,status,version) from t_goods where id=#{id}

2.根据商品信息生成订单

3.修改商品status为2

update goods

set status=2,version=version+1

where id=#{id} and version=#{version};

为了使用乐观锁,我们首先修改goods表,增加一个version字段,数据默认version值为1。

goods表初始数据如下:

mysql> select * from goods;  
+----+--------+------+---------+  
| id | status | name | version |  
+----+--------+------+---------+  
|  1 |      1 | 小米 |       1 |  
|  2 |      2 | 华为 |       2 |  
+----+--------+------+---------+  
2 rows in set  mysql>  

对于乐观锁的实现,我使用MyBatis来进行实践,具体如下:


Goods实体类:

/** * ClassName: Goods <br/> * Function: 商品实体. <br/> * @author lz*/  
public class Goods implements Serializable {  /** * serialVersionUID:序列化ID. */  private static final long serialVersionUID = 6803791908148880587L;  /** * id:主键id. */  private int id;  /** * status:商品状态:1未下单、2已下单. */  private int status;  /** * name:商品名称. */  private String name;  /** * version:商品数据版本号. */  private int version;  @Override  public String toString(){  return "good id:"+id+",goods status:"+status+",goods name:"+name+",goods version:"+version;  }  //setter and getter  }  

GoodsDao

/** * updateGoodsUseCAS:使用CAS(Compare and set)更新商品信息. <br/> * * @author lz* @param goods 商品对象 * @return 影响的行数 */  
int updateGoodsUseCAS(Goods goods);  

mapper.xml

<update id="updateGoodsUseCAS" parameterType="Goods">  update goods set status=#{status},name=#{name},version=version+1 where id=#{id} and version=#{version}   
</update>  

GoodsDaoTest测试类

@Test  
public void goodsDaoTest(){  int goodsId = 1;  //根据相同的id查询出商品信息,赋给2个对象  Goods goods1 = this.goodsDao.getGoodsById(goodsId);  Goods goods2 = this.goodsDao.getGoodsById(goodsId);  //打印当前商品信息  System.out.println(goods1);  System.out.println(goods2);  //更新商品信息1  goods1.setStatus(2);//修改status为2  int updateResult1 = this.goodsDao.updateGoodsUseCAS(goods1);  System.out.println("修改商品信息1"+(updateResult1==1?"成功":"失败"));  //更新商品信息2  goods2.setStatus(2);//修改status为2  int updateResult2 = this.goodsDao.updateGoodsUseCAS(goods2);  System.out.println("修改商品信息2"+(updateResult2==1?"成功":"失败"));  
}  

输出结果:

good id:1,goods status:1,goods name:道具,goods version:1  
good id:1,goods status:1,goods name:道具,goods version:1  
修改商品信息1成功  
修改商品信息2失败
mysql> select * from goods;  
+----+--------+------+---------+  
| id | status | name | version |  
+----+--------+------+---------+  
|  1 |      2 | 小米 |       2 |  
|  2 |      2 | 华为 |       2 |  
+----+--------+------+---------+  
2 rows in set  

总结

其实这种版本号的方法并不是适用于所有的乐观锁场景。举个例子,当电商抢购活动时,大量并发进入,如果仅仅使用版本号或者时间戳,就会出现大量的用户查询出库存存在,但是却在扣减库存时失败了,而这个时候库存是确实存在的。想象一下,版本号每次只会有一个用户扣减成功,不可避免的人为造成失败。


http://chatgpt.dhexx.cn/article/UBNFXm6S.shtml

相关文章

Redis解决高并发问题

1 模拟商品抢购和并发的效果 这里模拟一个商品抢购的过程所带来的问题&#xff0c;以及解决问题的思路。 这里模拟的商品抢购过程是一个商品正常购买的过程&#xff0c;其中包含了两个主要的步骤&#xff1a;商品库存减少和商品购买记录的添加。 下面搭建项目环境。 1.1 数…

一文教你如何处理高并发

目录 前言 一、为什么要解决高并发问题 二、性能评估 计算峰值流量方法 本章结论 三、性能测试 测试目的 找到系统最高承受压力的临界点 找出系统中的短板 测试工具 简单测试 1.数据抓包 2.加压测试 3.硬件跟踪 4.JVM跟踪 5.其它组件测试 6.总括 全链路测试&…

高并发场景设计与解决方案

所有的平台或系统建设和维护中&#xff0c;高并发场景都存在&#xff0c;解决方案也是各种样式&#xff0c;本次将从初中、高二个场景给出设计方案。 本文内容&#xff1a;高并发场景定义&#xff0c;高并发初中级场景与解决方案&#xff0c;高并发高级场景与解决方案 第一部分…

数据库关系代数运算

转载&#xff1a;https://wenku.baidu.com/view/f301bf48e45c3b3567ec8b75.html

数据库关系模型与关系运算---2022.2.13

关于外模式&#xff0c;模式&#xff0c;内模式的理解 可以看到用不同的语句进行表示&#xff1a; 关系的性质 概念模式/内模式映射是物理独立性的关键&#xff1b; 外模式/概念模式映射就是逻辑独立性的关键 候选键 (最小组成的超键) 关系中的一个属性组&#xff0c;其值…

关系运算

关系代数是一种抽象的查询语言&#xff0c;它用对关系的运算来表达查询。关系运算的运算对象是关系&#xff0c;运算结果亦是关系&#xff0c;关系代数的运算符包括两类&#xff1a;传统的集合运算和专门的关系运算两类。 传统的集合运算是从关系的水平方向&#xff0c;即行的角…

数据库之间的关系

数据库的设计 1.多表之间的关系 1.一对一&#xff1a;如 人和身份证 &#xff0c;一个人只能一张身份证&#xff0c;一个身份证只能对应一个人 2.一对多&#xff1a;如 部门和员工 一个部门有多个员工&#xff0c;一个员工只能对应一个部门 3.多对多&#xff1a…

数据库(笔记)——关系代数以及相关运算

关系代数 关系代数及其运算符集合运算符关系运算符 总结 关系代数及其运算符 关系代数是一种抽象的查询语言&#xff0c;通过关系的运算来表达查询 关系代数常使用的运算符由如下几类 集合运算符&#xff1a;∪&#xff08;并&#xff09;、∩&#xff08;交&#xff09;、-&…

数据库关系代数详解

文章目录 数据库关系代数1. 传统的关系运算2. 专门的关系运算2.1 关系运算中的基础概念2.2 元组的连接2.3 象集(除法运算重要工具) 3 数学上的运算3.1 并运算3.2 差运算3.3 交运算3.4 笛卡尔积&#xff08;万能运算&#xff09; 4. 关系运算4.1 表格简介4.2 选择&#xff08;Se…

数据库专门的关系运算

本文章用表 选择运算&#xff08;从行的角度运算&#xff09; 选择又称为限制&#xff0c;选择运算符的含义&#xff1a; 在关系R中选择满足给定条件的诸元组 投影&#xff08;从列的角度运算&#xff09; 投影运算符的含义&#xff1a;从表中选出若干属性列组成新的关系 注…

数据库关系代数运算之连接

联接有三种&#xff1a;θ联接和自然联接&#xff08;这里是算术比较符&#xff09;&#xff0c;外联接。 &#xff08;1&#xff09; θ联接 (从R和S的笛卡儿乘积中选取满足条件“iθj”的元组 •&#xff08;2&#xff09;自然联接&#xff08;naturaljoin&#xff09; 两个…

数据库关系代数中除运算讲解和SQL语句的实现

【数据库原理】关系代数篇——除法讲解 陈宇超 编辑总结: 除法运算的一般形式示意图 如何计算RS呢&#xff0c;首先我们引进”象集”的概念&#xff0c;具体意义看下面的陈述即可理解 关系R和关系S拥有共同的属性B、C , RS得到的属性值就是关系R包含而关系S不包含的属性&am…

关系代数基本运算 数据库

操作目录 关系代数的八种基本运算并交差笛卡尔积选择投影连接除总结 关系代数的八种基本运算 并 并&#xff0c;就是将两个或多个表并连起来&#xff0c;需要注意的就是在并的过程中&#xff0c;我们并不是直接一笼统地并起来&#xff0c;而且还要对相同的元祖进行合并&#x…

数据库系统概论----关系运算之除运算

这一周都在复习《数据库系统概论》这门课&#xff0c;看到关系运算的这一节时&#xff0c;对于除运算不是很理解。 通过百度&#xff0c;我觉得也没有得到比较容易理解的讲解。 这里呢&#xff0c;我就分享一下我的理解吧&#xff0c;如有差错的地方&#xff0c;还希望看到这…

数据库-----关系运算

关系数据库概述 相关术语 ◎在现实世界中&#xff0c;描述一个事物常常要抽取其若干特征来表示&#xff0c;这些特征称为属性&#xff0c;如用学号、性别、班级等来描述学生。每个属性的取值范围对应一个值的集合&#xff0c;称为属性的域&#xff0c;如性别的域是{男&#x…

数据库基础--关系代数中的除法运算

除法运算的定义&#xff1a; 这个概念的描述的非常抽象&#xff0c;刚开始学习的同学完全不知所云。这里通过一个实例来说明除法运算的求解过程 设有关系R、S 如图所示&#xff0c;求RS 的结果 求解步骤过程&#xff1a; 第一步&#xff1a;找出关系R和关系S中相同的属…

数据库的运算

数据库的运算可分为集合运算和关系运算。 一、集合运算 • 从关系的水平方向迚行&#xff1b; • 包括&#xff0c;幵、交、差、笛卡尔积运算。 • 幵运算&#xff08;R U S&#xff09;&#xff1a;可实现数据的揑入。 • 差运算&#xff08;R–S&#xff09;&#xff1a;主…

关系数据库:专门关系运算

专门关系运算有&#xff1a;选择&#xff0c;投影&#xff0c;连接&#xff0c;除运算。 1.选择从关系中找出满足给定条件的所有元组称为选择&#xff0c;其中条件是用逻辑表达式给出的&#xff0c;逻辑表达式为真时元组被选取。 选择运算记为δF&#xff08;R&#xff09;&am…

详解【数据库】关系代数基本运算

文章目录 五中基本的关系代数操作并&#xff08;Union&#xff09;差&#xff08;Difference&#xff09;广义笛卡尔积&#xff08;Extended Cartesian Product&#xff09;投影&#xff08;Projection&#xff09;选择&#xff08;Selection&#xff09; 连接等值连接自然连接…

数据库关系运算——除运算

书上给“除运算”的定义是&#xff1a; 设关系R除以关系S的结果为关系T&#xff0c;则T包含所有在R但不在S中的属性及其值&#xff0c;且T的元组与S的元组的所有组合都在R中。 我对此不是很理解。 直到看到这样的解读&#xff0c;方才恍然大悟&#xff1a;