[数据库锁机制] 深入理解乐观锁、悲观锁以及CAS乐观锁的实现机制原理分析

  • 时间:
  • 浏览:0
  • 来源:幸运快3_快3平台代理_幸运快3平台代理

前言:

  • 在并发访问情况汇报下,不可能 会出现脏读、不可重复读和幻读等读问題,为了应对那些问題,主流数据库都提供了锁机制,并引入了事务隔离级别的概念。数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同時 存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性。
  • 乐观并发控制(乐观锁)和悲观并发控制(悲观锁)是并发控制主要采用的技术手段。无论是悲观锁还是乐观锁,详细都是亲们定义出来的概念,还前要认为是并详细都是思想。真是 不仅仅是关系型数据库系统所含乐观锁和悲观锁的概念,像memcache、hibernate、tair等详细都是类似于于的概念。
  • 本文中也将深入分析一下乐观锁的实现机制,介绍那些是CAS、CAS的应用以及CAS趋于稳定的问題等。

并发控制

在计算机科学,有点是多线程池池 设计、操作系统、多外理机和数据库等领域,并发控制(Concurrency control)是确保及时纠正由并发操作由于的错误的并详细都是机制。

数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同時 存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性。下面举例说明并发操作带来的数据不一致性问題:

现有两处火车票售票点,同時 读取某一趟列车车票数据库中车票余额为 X。两处售票点同時 卖出一张车票,同時 修改余额为 X -1写回数据库,本来 就造成了实际卖出两张火车票而数据库中的记录却只少了一张。 产生你是什么 情况汇报的由于是不可能 另一一两个事务读入同一数据并同時 修改,其中另一一两个事务提交的结果破坏了本来 事务提交的结果,由于其数据的修改被丢失,破坏了事务的隔离性。并发控制要外理的本来 类似于于问題。

封锁、时间戳、乐观并发控制(乐观锁)和悲观并发控制(悲观锁)是并发控制主要采用的技术手段。

一、数据库的锁

当并发事务同時 访问另一一两个资源时,有不可能 由于数据不一致,之后前要并详细都是机制来将数据访问顺序化,以保证数据库数据的一致性。锁本来 其中的并详细都是机制。

在计算机科学中,锁是在执行多线程池池 时用于强行限制资源访问的同步机制,即用于在并发控制中保证对互斥要求的满足。

锁的分类(oracle)

一、按操作划分,可分为DML锁DDL锁

二、按锁的粒度划分,可分为表级锁行级锁页级锁(mysql)

三、按锁级别划分,可分为共享锁排他锁

四、按加锁方式划分,可分为自动锁显示锁

五、按使用方式划分,可分为乐观锁悲观锁

DML锁(data locks,数据锁),用于保护数据的详细性,其中包括行级锁(Row Locks (TX锁))、表级锁(table lock(TM锁))。

DDL锁(dictionary locks,数据字典锁),用于保护数据库对象的价值形式,如表、索引等的价值形式定义。其中包排他DDL锁(Exclusive DDL lock)、共享DDL锁(Share DDL lock)、可中断解析锁(Breakable parse locks)

1.1 锁机制

常用的锁机制有并详细都是:

1、悲观锁:假定会趋于稳定并发冲突,屏蔽一切不可能 违反数据详细性的操作。悲观锁的实现,往往依靠底层提供的锁机制;悲观锁会由于其它所有前要锁的线程池池 挂起,等待持有锁的线程池池 释放锁。

2、乐观锁:假设不需要趋于稳定并发冲突,每次不加锁本来 假设没有了冲突而去完成某项操作,只在提交操作时检查算不算违反数据详细性。不可能 不可能 冲突失败就重试,直到成功为止。乐观锁大多是基于数据版本记录机制实现。为数据增加另一一两个版本标识,比如在基于数据库表的版本外理方案中,一般是通过为数据库表增加另一一两个 “version” 字段来实现。读取出数据时,将此版本号同時 读出,之前 更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,不可能 提交的数据版本号大于数据库表当前版本号,则予以更新,之后认为是过期数据。 

乐观锁的缺点是只能外理每段脏读的问題,类似于于ABA问題(下面会讲到)。

在实际生产环境上边,不可能 并发量不大且不允许脏读,还前要使用悲观锁外理并发问題;但不可能 系统的并发非常大话语,悲观锁定会带来非常大的性能问題,本来 亲们儿就要选折 乐观锁定的方式。

二、悲观锁与乐观锁详解

2.1 悲观锁

在关系数据库管理系统里,悲观并发控制(叫金“悲观锁”,Pessimistic Concurrency Control,缩写“PCC”)是并详细都是并发控制的方式。它还前要阻止另一一两个事务以影响某些用户的方式来修改数据。不可能 另一一两个事务执行的操作都某行数据应用了锁,那只能当你是什么 事务把锁释放,某些事务助于够执行与该锁冲突的操作。

悲观并发控制主要用于数据争用激烈的环境,以及趋于稳定并发冲突时使用锁保护数据的成本要低于回滚事务的成本的环境中。

悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的某些事务,以及来自内部人员系统的事务外理)修改持保守态度(悲观),之后,在整个数据外理过程中,将数据趋于稳定锁定情况汇报。 悲观锁的实现,往往依靠数据库提供的锁机制 (也只能数据库层提供的锁机制助于真正保证数据访问的排他性,之后,即使在本系统中实现了加锁机制,也无法保证内部人员系统不需要修改数据)

在数据库中,悲观锁的流程如下:

在对任意记录进行修改前,先尝试为该记录换成排他锁(exclusive locking)。

不可能 加锁失败,说明该记录正在被修改,没有了当前查询不可能 要等待不可能 抛出异常。 具体响应方式由开发者根据实际前要决定。

不可能 成功加锁,没有了就还前要对记录做修改,事务完成后就会解锁了。

其间不可能 有某些对该记录做修改或加排他锁的操作,详细都是等待亲们儿解锁或直接抛出异常。

MySQL InnoDB中使用悲观锁:

要使用悲观锁,亲们儿前要关闭mysql数据库的自动提交属性,不可能 MySQL默认使用autocommit模式,也本来 说,当你执行另一一两个更新操作后,MySQL会立刻将结果进行提交。set autocommit=0;

//0.开始英语

了了英文事务
begin;/begin work;/start transaction; (三者选一就还前要)
//1.查询出商品信息
select status from t_goods where id=1 for update;
//2.根据商品信息生成订单
insert into t_orders (id,goods_id) values (null,1);
//3.修改商品status为2
update t_goods set status=2;
//4.提交事务
commit;/commit work;

上边的查询话语中,亲们儿使用了select…for update的方式,本来 就通过开启排他锁的方式实现了悲观锁。此时在t_goods表中,id为1的 那条数据就被亲们儿锁定了,其它的事务前要等本次事务提交之前 助于执行。本来 亲们儿还前要保证当前的数据不需要被其它事务修改。

上边亲们儿提到,使用select…for update会把数据给锁住,不过亲们儿前要注意某些锁的级别,MySQL InnoDB默认行级锁。行级锁详细都是基于索引的,不可能 第三根SQL话语用只能索引是不需要使用行级锁的,会使用表级锁把整张表锁住,这点前要注意。

优点与欠缺

悲观并发控制实际上是“先取锁再访问”的保守策略,为数据外理的安全提供了保证。之后在强度方面,外理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的不可能 ;另外,在只读型事务外理中不可能 不需要产生冲突,也没必要使用锁,本来 做只能增加系统负载;还有会降低了并行性,另一一两个事务不可能 锁定了某行数据,某些事务就前要等待该事务外理完才还前要外理那行数

2.2 乐观锁

在关系数据库管理系统里,乐观并发控制(叫金“乐观锁”,Optimistic Concurrency Control,缩写“OCC”)是并详细都是并发控制的方式。它假设多用户并发的事务在外理时不需要彼此互相影响,各事务助于在不产生锁的情况汇报下外理每其他人影响的那每段数据。在提交数据更新之前 ,每个事务会先检查在该事务读取数据后,有没有了某些事务又修改了该数据。不可能 某些事务有更新话语,正在提交的事务会进行回滚。乐观事务控制最早是由孔祥重(H.T.Kung)教授提出。

乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况汇报下不需要造成冲突,本来 在数据进行提交更新的之前 ,才会正式对数据的冲突算不算进行检测,不可能 发现冲突了,则让返回用户错误的信息,让用户决定如保去做。

相对于悲观锁,在对数据库进行外理的之前 ,乐观锁何必 会使用数据库提供的锁机制。一般的实现乐观锁的方式本来 记录数据版本。

数据版本,为数据增加的另一一两个版本标识。当读取数据时,将版本标识的值同時 读出,数据每更新一次,同時 对版本标识进行更新。当亲们儿提交更新的之前 ,判断数据库表对应记录的当前版本信息与第一次取出来的版本标识进行比对,不可能 数据库表当前版本号与第一次取出来的版本标识值相等,则予以更新,之后认为是过期数据。

实现数据版本有并详细都是方式,第并详细都是是使用版本号,第二种是使用时间戳。

使用版本号实现乐观锁

使用版本号时,还前要在数据初始化时指定另一一两个版本号,每次对数据的更新操作都对版本号执行+1操作。并判断当前版本号是详细都是该数据的最新的版本号。

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};

优点与欠缺

乐观并发控制相信事务之间的数据竞争(data race)的概率是比较小的,之后尽不可能 直接做下去,直到提交的之前 才去锁定,本来 不需要产生任何锁和死锁。但不可能 直接简单没有了做,还是有不可能 会遇到不可预期的结果,类似于于另一一两个事务都读取了数据库的某一行,经过修改之前 写回数据库,这时就遇到了问題。

三、CAS详解

在说CAS之前 ,亲们儿不得不提一下Java的线程池池 安全问題。

线程池池 安全:

众所周知,Java是多线程池池 的。之后,Java对多线程池池 的支持真是 是一把双刃剑。一旦涉及到多个线程池池 操作共享资源的情况汇报时,外理不好就不可能 产生线程池池 安全问題。线程池池 安全性不可能 是非常冗杂的,在没有了宽裕的同步的情况汇报下,多个线程池池 中的操作执行顺序是不可预测的。

Java上边进行多线程池池 通信的主要方式本来 共享内存的方式,共享内存主要的关注点有另一一两个:可见性和有序性。换成复合操作的原子性,亲们儿还前要认为Java的线程池池 安全性问題主要关注点有一两个:可见性、有序性和原子性。

Java内存模型(JMM)外理了可见性和有序性的问題,而锁外理了原子性的问題。这里不再详细介绍JMM及锁的某些相关知识。之后亲们儿要讨论另一一两个问題,那本来 锁到底是详细都是有利无弊的?

3.1 锁趋于稳定的问題

Java在JDK1.5之前 详细都是靠synchronized关键字保证同步的,你是什么 通过使用一致的锁定协议来协调对共享情况汇报的访问,还前要确保无论哪个线程池池 持有共享变量的锁,都采用独占的方式来访问那些变量。独占锁真是 本来 并详细都是悲观锁,本来 还前要说synchronized是悲观锁。

悲观锁机制趋于稳定以下问題:

1) 在多线程池池 竞争下,加锁、释放锁会由于比较多的上下文切换和调度延时,引起性能问題。

2) 另一一两个线程池池 持有锁会由于其它所有前要此锁的线程池池 挂起。

3) 不可能 另一一两个优先级高的线程池池 等待另一一两个优先级低的线程池池 释放锁会由于优先级倒置,引起性能风险。

而本来 更加有效的锁本来 乐观锁。所谓乐观锁本来 ,每次不加锁本来 假设没有了冲突而去完成某项操作,不可能 不可能 冲突失败就重试,直到成功为止。

与锁相比,volatile变量是另一一两个更轻量级的同步机制,不可能 在使用那些变量时不需要趋于稳定上下文切换和线程池池 调度等操作,之后volatile只能外理原子性问題,之后当另一一两个变量依赖旧值时就只能使用volatile变量。之后对于同步最终还是要回到锁机制上来。

乐观锁

乐观锁( Optimistic Locking)真是 是并详细都是思想。相对悲观锁而言,乐观锁假设认为数据一般情况汇报下不需要造成冲突,本来 在数据进行提交更新的之前 ,才会正式对数据的冲突算不算进行检测,不可能 发现冲突了,则让返回用户错误的信息,让用户决定如保去做。

上边提到的乐观锁的概念中真是 不可能 阐述了他的具体实现细节:

主要本来 另一一两个步骤:冲突检测数据更新

真是 现方式有并详细都是比较典型的本来 Compare and Swap(CAS)。

3.2 CAS

CAS是项乐观锁技术,当多个线程池池 尝试使用CAS同時 更新同另一一两个变量时,只能其中另一一两个线程池池 能更新变量的值,而其它线程池池 都失败,失败的线程池池 何必 会被挂起,本来 被告知这次竞争中失败,并还前要再次尝试。

CAS 操作所含另一一两个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。不可能 内存位置的值与预期原值相匹配,没有了外理器会自动将该位置值更新为新值。之后,外理器不做任何操作。无论哪种情况汇报,它详细都是在 CAS 指令之前 返回该位置的值。(在 CAS 的某些特殊情况汇报下将仅返回 CAS 算不算成功,而不提取当前值。)CAS 有效地说明了“我认为位置 V 应该所含值 A;不可能 所含该值,则将 B 装进去你是什么 位置;之后,何必 更改该位置,只谁能告诉我你是什么 位置现在的值即可。”这真是 和乐观锁的冲突检查+数据更新的原理是一样的。

这里再强调一下,乐观锁是并详细都是思想。CAS是你是什么 思想的并详细都是实现方式。

3.3 Java对CAS的支持

JDK 5之前 Java语言是靠synchronized关键字保证同步的,这是并详细都是独占锁,也是是悲观锁。j在JDK1.5 中新增java.util.concurrent(J.U.C)本来 建立在CAS之上的。相对于对于synchronized你是什么 阻塞算法,CAS是非阻塞算法的并详细都是常见实现。本来 J.U.C在性能上有了很大的提升。

现代的CPU提供了特殊的指令,允许算法执行读-修改-写操作,而不需要害怕某些线程池池 同時 修改变量,不可能 不可能 某些线程池池 修改变量,没有了CAS会检测它(并失败),算法还前要对该操作重新计算。而 compareAndSet() 就用那些代替了锁定。

亲们儿以java.util.concurrent中的AtomicInteger为例,看一下在没有了锁的情况汇报下是如保保证线程池池 安全的。主要理解getAndIncrement方式,该方式的作用大约 ++i 操作。

public class AtomicInteger extends Number implements java.io.Serializable {
    
    private volatile int value;
    
    public final int get() {
        return value;
    }
    
    public final int getAndIncrement() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return current;
        }
    }
    
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

字段value前要借助volatile原语,保证线程池池 间的数据是可见的(共享的)。本来 在获取变量的值的之前 助于直接读取。之之后看看++i是缘何做到的。getAndIncrement采用了CAS操作,每次从内存中读取数据之后将此数据和+1后的结果进行CAS操作,不可能 成功就返回结果,之后重试直到成功为止。而compareAndSet利用JNI来完成CPU指令的操作。

public final boolean compareAndSet(int expect, int update) {   
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
 }

整体的过程本来 本来 子的,利用CPU的CAS指令,同時 借助JNI来完成Java的非阻塞算法。其它原子操作详细都是利用类似于于的价值形式完成的。

而整个J.U.C详细都是建立在CAS之上的,之后对于synchronized阻塞算法,J.U.C在性能上有了很大的提升。

3.4 CAS会由于“ABA问題”:

ABA问題:

aba实际上是乐观锁无法外理脏数据读取的并详细都是体现。CAS算法实现另一一两个重要前提前要取出内存中某时刻的数据,而在下时刻比较并替换,没有了在你是什么 时间差类会由于数据的变化。

比如说另一一两个线程池池 one从内存位置V中取出A,这之前 本来 线程池池 two也从内存中取出A,之后two进行了某些操作变成了B,之后two又将V位置的数据变成A,这之前 线程池池 one进行CAS操作发现内存中仍然是A,之后one操作成功。尽管线程池池 one的CAS操作成功,之后不代表你是什么 过程本来 没有了问題的。

每段乐观锁的实现是通过版本号(version)的方式来外理ABA问題,乐观锁每次在执行数据的修改操作时,详细都是带上另一一两个版本号,一旦版本号和数据的版本号一致就还前要执行修改操作并对版本号执行+1操作,之后就执行失败。不可能 每次操作的版本号详细都是随之增加,本来 不需要出现ABA问題,不可能 版本号只会增加不需要减少。

 不可能 链表的头在变化了两次后恢复了原值,之后不代表链表就没有了变化。之后AtomicStampedReference/AtomicMarkableReference就很有用了。

AtomicMarkableReference 类描述的另一一两个<Object,Boolean>的对,还前要原子的修改Object不可能 Boolean的值,你是什么 数据价值形式在某些缓存不可能 情况汇报描述中比较有用。你是什么 价值形式在单个不可能 同時 修改Object/Boolean的之前 助于有效的提高吞吐量。 



AtomicStampedReference 类维护所含整数“标志”的对象引用,还前要用原子方式对其进行更新。对比AtomicMarkableReference 类的<Object,Boolean>,AtomicStampedReference 维护的是并详细都是类似于于<Object,int>的数据价值形式,真是 本来 对对象(引用)的另一一两个并发计数(标记版本戳stamp)。之后与AtomicInteger 不同的是,此数据价值形式还前要携带另一一两个对象引用(Object),然还助于够对此对象和计数同時 进行原子操作。

REFERENCE:

采集自以下博客:

1.  http://www.hollischuang.com/archives/934

2.  http://www.hollischuang.com/archives/1537

3.  http://www.cnblogs.com/Mainz/p/3546347.html

4.  http://www.digpage.com/lock.html

5.  https://chenzhou123520.iteye.com/blog/1863407

6.  https://chenzhou123520.iteye.com/blog/18150954

猜你喜欢

Xbox老大:主机失去意义,串流可服务20亿玩家

从几年前现在开使,就因为着越来越人论断“下一代主机将是最后一代主机,因为着串流技术和云技术因为着让主机硬件拖累意义”,现在随着现实的技术越来越接近你这一想法,或许你这一未来离亲

2020-01-25

英倡组“欧洲海军”保卫波斯湾

图:伊朗舰队19日包围英国油轮美联社【大公报讯】据法新社及彭博社报道:伊朗19日扣押一艘英国油轮,以报复英国海军月初在直布罗陀扣押伊朗油船,大幅加剧了区域紧张局势。英国22日表

2020-01-25

世界上最小的磁体诞生:IBM实现在单原子上存储位数据

别问我当当让我们儿与否愿意幻想过愿意的场景:有朝一日当当让我们儿要能将iTunes曲库中的35000万首歌曲存储在一张非要信用卡大小的设备当中。尽管愿意的理想很美好,然而目前的

2020-01-25

鲁大师2018假机排行榜公布三星苹果很受伤

【TechWeb】伴随着线上购物的日趋便利,如今购买智能手机时候很容易的对假货进行有效的控制。然而,在其他三四线城市,假货手机依旧在路边小店中抢占着正版机型的市场。近期,鲁大师

2020-01-25

苹果iphone6 plus美版三网通杀仅售4900

【IT168 上海行情】苹果564 机苹果564 机6Plus给人的第一感觉可是大,它正面配备一块5.5英寸屏幕,手机整体尺寸即便在Android手机中也是算得上的大屏了。

2020-01-25