前语
mysql中的达观锁是怎样完成的?许多新手对此不是很清楚,为了协助咱们解决这个难题,下面将为咱们具体讲解,有这方面需求的人能够来学习下,希望你能有所收成。
一、达观锁
达观锁( Optimistic Locking ) 相对悲观锁而言,达观锁机制采取了愈加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制完成,以保证操作最大程度的独占性。但随之而来的便是数据库功能的很多开支,特别是对长业务而言,这样的开支往往无法承受。
而达观锁机制在必定程度上解决了这个问题。达观锁,大多是依据数据版别( Version )记载机制完成。何谓数据版别?即为数据添加一个版别标识,在依据数据库表的版别解决方案中,一般是通过为数据库表添加一个 “version” 字段来完成。读取出数据时,将此版别号一起读出,之后更新时,对此版别号加一。
此刻,将提交数据的版别数据与数据库表对应记载的当时版别信息进行比对,假如提交的数据版别号大于数据库表当时版别号,则予以更新,不然认为是过期数据。
长处:
从上面的例子能够看出,达观锁机制避免了长业务中的数据库加锁开支(操作员 A和操作员 B 操作过程中,都没有对数据库数据加锁),大大提升了大并发量下的体系全体功能体现。
缺陷:
需求留意的是,达观锁机制往往依据体系中的数据存储逻辑,因而也具备必定的局限性,如在上例中,由于达观锁机制是在咱们的体系中完成,来自外部体系的用户余额更新操作不受咱们体系的操控,因而可能会造成脏数据被更新到数据库中。在体系设计阶段,咱们应该充分考虑到这些情况呈现的可能性,并进行相应调整(如将达观锁策略在数据库存储过程中完成,对外只开放依据此存储过程的数据更新途径,而不是将数据库表直接对外公开)。
二、怎样完成达观锁呢,一般来说有以下2种方法
2.1、运用数据版别(Version)记载机制完成
这是达观锁最常用的一种完成 方法。何谓数据版别?即为数据添加一个版别标识,一般是通过为数据库表添加一个数字类型的 “version” 字段来完成。当读取数据时,将version字段的值一起读出,数据每更新一次,对此version值加一。
当咱们提交更新的时候,判断数据库表对应记载 的当时版别信息与第一次取出来的version值进行比对,假如数据库表当时版别号与第一次取出来的version值相等,则予以更新,不然认为是过期数 据。用下面的一张图来阐明:
如上图所示,假如更新操作次序履行,则数据的版别(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来进行实践,具体如下:
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};
这样咱们就完成了达观锁。