MySQl中的乐观锁是怎么实现的

admin1年前笔记117

前语

mysql中的达观锁是怎样完成的?许多新手对此不是很清楚,为了协助咱们解决这个难题,下面将为咱们具体讲解,有这方面需求的人能够来学习下,希望你能有所收成。

一、达观锁

达观锁( Optimistic Locking ) 相对悲观锁而言,达观锁机制采取了愈加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制完成,以保证操作最大程度的独占性。但随之而来的便是数据库功能的很多开支,特别是对长业务而言,这样的开支往往无法承受。

而达观锁机制在必定程度上解决了这个问题。达观锁,大多是依据数据版别( Version )记载机制完成。何谓数据版别?即为数据添加一个版别标识,在依据数据库表的版别解决方案中,一般是通过为数据库表添加一个 “version” 字段来完成。读取出数据时,将此版别号一起读出,之后更新时,对此版别号加一。

此刻,将提交数据的版别数据与数据库表对应记载的当时版别信息进行比对,假如提交的数据版别号大于数据库表当时版别号,则予以更新,不然认为是过期数据。

长处

从上面的例子能够看出,达观锁机制避免了长业务中的数据库加锁开支(操作员 A和操作员 B 操作过程中,都没有对数据库数据加锁),大大提升了大并发量下的体系全体功能体现。

缺陷

需求留意的是,达观锁机制往往依据体系中的数据存储逻辑,因而也具备必定的局限性,如在上例中,由于达观锁机制是在咱们的体系中完成,来自外部体系的用户余额更新操作不受咱们体系的操控,因而可能会造成脏数据被更新到数据库中。在体系设计阶段,咱们应该充分考虑到这些情况呈现的可能性,并进行相应调整(如将达观锁策略在数据库存储过程中完成,对外只开放依据此存储过程的数据更新途径,而不是将数据库表直接对外公开)。

二、怎样完成达观锁呢,一般来说有以下2种方法

2.1、运用数据版别(Version)记载机制完成

这是达观锁最常用的一种完成 方法。何谓数据版别?即为数据添加一个版别标识,一般是通过为数据库表添加一个数字类型的 “version” 字段来完成。当读取数据时,将version字段的值一起读出,数据每更新一次,对此version值加一。

当咱们提交更新的时候,判断数据库表对应记载 的当时版别信息与第一次取出来的version值进行比对,假如数据库表当时版别号与第一次取出来的version值相等,则予以更新,不然认为是过期数 据。用下面的一张图来阐明:


image.png


如上图所示,假如更新操作次序履行,则数据的版别(version)顺次递增,不会发生抵触。但是假如发生有不同的业务操刁难同一版别的数据进行修 改,那么,先提交的操作(图中B)会把数据version更新为2,当A在B之后提交更新时发现数据的version现已被修正了,那么A的更新操作会失利。

2.2、达观确定的第二种完成方法和第一种差不多

同样是在需求达观锁操控的table中添加一个字段,名称无所谓,字段类型运用时间戳 (timestamp), 和上面的version相似,也是在更新提交的时候查看当时数据库中数据的时间戳和自己更新前取到的时间戳进行对比,假如共同则OK,不然便是版别抵触。

运用举例:

以MySQL InnoDB为例

仍是拿之前的实例来举:产品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 t_goods
set status=2,version=version+1
where id=#{id} and version=#{version};

那么为了运用达观锁,咱们首要修正t_goods表,添加一个version字段,数据默认version值为1。

t_goods表初始数据如下:

mysql> select * from t_goods;
+----+--------+------+---------+  
| id | status | name | version |  
+----+--------+------+---------+  
|  1 |      1 | 道具 |       1 |  
|  2 |      2 | 装备 |       2 |  
+----+--------+------+---------+  
2 rows in set  
mysql>

对于达观锁的完成,我运用MyBatis来进行实践,具体如下:

Goods实体类

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/> 
 * 
 */  
int updateGoodsUseCAS(Goods goods);
mapper.xml<update id="updateGoodsUseCAS" parameterType="Goods">  
    <![CDATA[ 
        update t_goods 
        set status=#{status},name=#{name},version=version+1 
        where id=#{id} and version=#{version} 
    ]]>  
</update>

GoodsDaoTest测验类

@Testpublic 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  
    goods1.setStatus(2);//修正status为2  
    int updateResult2 = this.goodsDao.updateGoodsUseCAS(goods1);  
    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失利

阐明:

在GoodsDaoTest测验方法中,咱们同时查出同一个版别的数据,赋给不同的goods目标,然后先修正good1目标然后履行更新操作,履行成功。然后咱们修正goods2,履行更新操作时提示操作失利。此刻t_goods表中数据如下:

mysql> select * from t_goods;
+----+--------+------+---------+  
| id | status | name | version |  
+----+--------+------+---------+  
|  1 |      2 | 道具 |       2 |  
|  2 |      2 | 装备 |       2 |  
+----+--------+------+---------+  
2 rows in set  
mysql>

咱们能够看到 id为1的数据version现已在第一次更新时修正为2了。所以咱们更新good2时update where条件现已不匹配了,所以更新不会成功,具体sql如下:

update t_goods
set status=2,version=version+1  
where id=#{id} and version=#{version};

这样咱们就完成了达观锁。


相关文章

从 0 开始了解 Docker

从 0 开始了解 Docker

序Docker 自开源以来受到了各大公司的广泛关注,或许现在互联网公司的运维体系不承载在 Docker(或 Pouch 等)之上都不好意思说自己的互联网公司。本文会简单介绍下 Docker 的基础概念...

深入的理解UDP编程

深入的理解UDP编程

什么是UDP?UDP是User Datagram Protocol(用户数据报协议)的缩写,它是一个简单的协议,简单到UDP规范RFC0768只有区区3页。UDP是工作在IP层之上的传输层协议,UDP...

linux下把进程绑定到特定cpu核上运行

对于普通的应用,操作系统的默认调度机制是没有问题的。但是,当某个进程需要较高的运行效率时,就有必要考虑将其绑定到单独的核上运行,以减小由于在不同的核上调度造成的开销。把某个进程/线程绑定到特定的cpu...

修复 Linux 上的文件权限错误

修复 Linux 上的文件权限错误

如果你通过网络或“跑腿网络”(将文件保存到硬盘,以将其复制到一台计算机)在两个用户之间共享文件,那么在尝试读取或写入文件时可能会遇到权限错误。即使你了解它的概念,你也可能不知道该如何诊断或解决问题。我...

git config实用指南

git config实用指南

git config介绍作为现代软件开发中不可或缺的工具之一,Git 不仅提供了强大的版本控制功能,还允许用户根据自己的需求进行个性化配置。其中,git config 命令是我们最常用的管理 Git...

await 错误捕获实现方式源码示例解析

Promise 是一种在 JavaScript 中用于处理异步操作的机制。Promise 在开发中被广泛使用,这篇文章将学习如何优雅的捕获 await 的错误。Promise 的使用方法创建一个 Pr...

发表评论    

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。