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

  • 时间:
  • 浏览:0
  • 来源:幸运快3_快3腾讯app_幸运快3腾讯app

前言:

  • 在并发访问状态下,怎么能让会突然出现脏读、不可重复读和幻读等读问提,为了应对哪些问提,主流数据库都提供了锁机制,并引入了事务隔离级别的概念。数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同去存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性。
  • 乐观并发控制(乐观锁)和悲观并发控制(悲观锁)是并发控制主要采用的技术手段。无论是悲观锁还是乐观锁,完整篇 都是其他同学定义出来的概念,时要认为是并是否是生活思想。我我觉得不仅仅是关系型数据库系统所含乐观锁和悲观锁的概念,像memcache、hibernate、tair等完整篇 都是累似 的概念。
  • 本文中也将深入分析一下乐观锁的实现机制,介绍哪些是CAS、CAS的应用以及CAS地处的问提等。

并发控制

在计算机科学,有点儿是多线程 设计、操作系统、多正确处理机和数据库等领域,并发控制(Concurrency control)是确保及时纠正由并发操作原困的错误的并是否是生活机制。

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

现有两处火车票售票点,同去读取某一趟列车车票数据库中车票余额为 X。两处售票点同去卖出一张车票,同去修改余额为 X -1写回数据库,越来越 就造成了实际卖出两张火车票而数据库中的记录却只少了一张。 产生这名 状态的原困是怎么能让另另1个 事务读入同一数据并同去修改,其中另另1个 事务提交的结果破坏了越来越 事务提交的结果,原困其数据的修改被丢失,破坏了事务的隔离性。并发控制要正确处理的可是累似 问提。

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

一、数据库的锁

当并发事务同去访问另另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、乐观锁:假设不用地处并发冲突,每次不加锁可是假设越来越 冲突而去完成某项操作,只在提交操作时检查是是是否是是违反数据完整篇 性。怎么能让怎么能让冲突失败就重试,直到成功为止。乐观锁大多是基于数据版本记录机制实现。为数据增加另另1个 版本标识,比如在基于数据库表的版本正确处理方案中,一般是通过为数据库表增加另另1个 “version” 字段来实现。读取出数据时,将此版本号同去读出,以前更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,怎么能让提交的数据版本号大于数据库表当前版本号,则予以更新,怎么能让认为是过期数据。 

乐观锁的缺点是越来越 正确处理帕累托图脏读的问提,累似 ABA问提(下面会讲到)。

在实际生产环境上方,怎么能让并发量不大且不允许脏读,时要使用悲观锁正确处理并发问提;但怎么能让系统的并发非常大一段话,悲观锁定会带来非常大的性能问提,好多好多 其他同学儿就要选用乐观锁定的方式。

二、悲观锁与乐观锁详解

2.1 悲观锁

在关系数据库管理系统里,悲观并发控制(叫安“悲观锁”,Pessimistic Concurrency Control,缩写“PCC”)是并是否是生活并发控制的方式。它时要阻止另另1个 事务以影响许多用户的方式来修改数据。怎么能让另另1个 事务执行的操作都某行数据应用了锁,那越来越 当这名 事务把锁释放,许多事务才不能执行与该锁冲突的操作。

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

悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的许多事务,以及来自内部人员系统的事务正确处理)修改持保守态度(悲观),怎么能让,在整个数据正确处理过程中,将数据地处锁定状态。 悲观锁的实现,往往依靠数据库提供的锁机制 (也越来越 数据库层提供的锁机制不能真正保证数据访问的排他性,怎么能让,即使在本系统中实现了加锁机制,也无法保证内部人员系统不用修改数据)

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

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

怎么能让加锁失败,说明该记录正在被修改,越来越 当前查询怎么能让要等待图片怎么能让抛出异常。 具体响应方式由开发者根据实际时要决定。

怎么能让成功加锁,越来越 就时要对记录做修改,事务完成后就会解锁了。

其间怎么能让有许多对该记录做修改或加排他锁的操作,不会 等待图片其他同学儿解锁或直接抛出异常。

MySQL InnoDB中使用悲观锁:

要使用悲观锁,其他同学儿时要关闭mysql数据库的自动提交属性,怎么能让MySQL默认使用autocommit模式,也可是说,当你执行另另1个 更新操作后,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一段话用越来越 索引是不用使用行级锁的,会使用表级锁把整张表锁住,这点时要注意。

优点与匮乏

悲观并发控制实际上是“先取锁再访问”的保守策略,为数据正确处理的安全提供了保证。怎么能让在数率方面,正确处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的怎么能让;另外,在只读型事务正确处理中怎么能让不用产生冲突,也没必要使用锁,越来越 做越来越 增加系统负载;还有会降低了并行性,另另1个 事务怎么能让锁定了某行数据,许多事务就时要等待图片该事务正确处理完才时要正确处理那行数

2.2 乐观锁

在关系数据库管理系统里,乐观并发控制(叫安“乐观锁”,Optimistic Concurrency Control,缩写“OCC”)是并是否是生活并发控制的方式。它假设多用户并发的事务在正确处理时不用彼此互相影响,各事务不能在不产生锁的状态下正确处理本人影响的那帕累托图数据。在提交数据更新以前,每个事务会先检查在该事务读取数据后,有越来越 许多事务又修改了该数据。怎么能让许多事务有更新一段话,正在提交的事务会进行回滚。乐观事务控制最早是由孔祥重(H.T.Kung)教授提出。

乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般状态下不用造成冲突,好多好多 在数据进行提交更新的以前,才会正式对数据的冲突是是是否是是进行检测,怎么能让发现冲突了,则让返回用户错误的信息,让用户决定怎么都能否去做。

相对于悲观锁,在对数据库进行正确处理的以前,乐观锁不须会使用数据库提供的锁机制。一般的实现乐观锁的方式可是记录数据版本。

数据版本,为数据增加的另另1个 版本标识。当读取数据时,将版本标识的值同去读出,数据每更新一次,同去对版本标识进行更新。当其他同学儿提交更新的以前,判断数据库表对应记录的当前版本信息与第一次取出来的版本标识进行比对,怎么能让数据库表当前版本号与第一次取出来的版本标识值相等,则予以更新,怎么能让认为是过期数据。

实现数据版本有并是否是生活方式,第并是否是生活是使用版本号,第二种是使用时间戳。

使用版本号实现乐观锁

使用版本号时,时要在数据初始化时指定另另1个 版本号,每次对数据的更新操作都对版本号执行+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)的概率是比较小的,怎么能让尽怎么能让直接做下去,直到提交的以前才去锁定,好多好多 不用产生任何锁和死锁。但怎么能让直接简单越来越 做,还是有怎么能让会遇到不可预期的结果,累似 另另1个 事务都读取了数据库的某一行,经过修改以前写回数据库,这时就遇到了问提。

三、CAS详解

在说CAS以前,其他同学儿不得不提一下Java的多线程 安全问提。

多线程 安全:

众所周知,Java是多多线程 的。怎么能让,Java对多多线程 的支持我我觉得是一把双刃剑。一旦涉及到多个多线程 操作共享资源的状态时,正确处理不好就怎么能让产生多线程 安全问提。多线程 安全性怎么能让是非常多样化的,在越来越 丰沛 的同步的状态下,多个多线程 中的操作执行顺序是不可预测的。

Java上方进行多多线程 通信的主要方式可是共享内存的方式,共享内存主要的关注点有另另1个 :可见性和有序性。再加复合操作的原子性,其他同学儿时要认为Java的多线程 安全性问提主要关注点有1个:可见性、有序性和原子性。

Java内存模型(JMM)正确处理了可见性和有序性的问提,而锁正确处理了原子性的问提。这里不再完整篇 介绍JMM及锁的许多相关知识。怎么能让其他同学儿要讨论另另1个 问提,那可是锁到底是完整篇 都是有利无弊的?

3.1 锁地处的问提

Java在JDK1.5以前完整篇 都是靠synchronized关键字保证同步的,这名 通过使用一致的锁定协议来协调对共享状态的访问,时要确保无论哪个多线程 持有共享变量的锁,都采用独占的方式来访问哪些变量。独占锁我我觉得可是并是否是生活悲观锁,好多好多 时要说synchronized是悲观锁。

悲观锁机制地处以下问提:

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

2) 另另1个 多线程 持有锁会原困其它所有时要此锁的多线程 挂起。

3) 怎么能让另另1个 优先级高的多线程 等待图片另另1个 优先级低的多线程 释放锁会原困优先级倒置,引起性能风险。

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

与锁相比,volatile变量是另另1个 更轻量级的同步机制,怎么能让在使用哪些变量时不用地处上下文切换和多线程 调度等操作,怎么能让volatile越来越 正确处理原子性问提,怎么能让当另另1个 变量依赖旧值时就越来越 使用volatile变量。怎么能让对于同步最终还是要回到锁机制上来。

乐观锁

乐观锁( Optimistic Locking)我我觉得是并是否是生活思想。相对悲观锁而言,乐观锁假设认为数据一般状态下不用造成冲突,好多好多 在数据进行提交更新的以前,才会正式对数据的冲突是是是否是是进行检测,怎么能让发现冲突了,则让返回用户错误的信息,让用户决定怎么都能否去做。

上方提到的乐观锁的概念中我我觉得怎么能让阐述了他的具体实现细节:

主要可是另另1个 步骤:冲突检测数据更新

我我觉得现方式并是否是生活生活比较典型的可是Compare and Swap(CAS)。

3.2 CAS

CAS是项乐观锁技术,当多个多线程 尝试使用CAS同去更新同另另1个 变量时,越来越 其中另另1个 多线程 能更新变量的值,而其它多线程 都失败,失败的多线程 不须会被挂起,可是被告知这次竞争中失败,并时要再次尝试。

CAS 操作所含另另1个 操作数 —— 内存位置(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算法实现另另1个 重要前提时要取出内存中某时刻的数据,而在下时刻比较并替换,越来越 在这名 时间差类会原困数据的变化。

比如说另另1个 多线程 one从内存位置V中取出A,这以前越来越 多线程 two也从内存中取出A,怎么能让two进行了许多操作变成了B,怎么能让two又将V位置的数据变成A,这以前多线程 one进行CAS操作发现内存中仍然是A,怎么能让one操作成功。尽管多线程 one的CAS操作成功,怎么能让不代表这名 过程可是越来越 问提的。

帕累托图乐观锁的实现是通过版本号(version)的方式来正确处理ABA问提,乐观锁每次在执行数据的修改操作时,不会 带上另另1个 版本号,一旦版本号和数据的版本号一致就时要执行修改操作并对版本号执行+1操作,怎么能让就执行失败。怎么能让每次操作的版本号不会 随之增加,好多好多 不用突然出现ABA问提,怎么能让版本号只会增加不用减少。

 怎么能让链表的头在变化了两次后恢复了原值,怎么能让不代表链表就越来越 变化。怎么能让AtomicStampedReference/AtomicMarkableReference就很有用了。

AtomicMarkableReference 类描述的另另1个 <Object,Boolean>的对,时要原子的修改Object怎么能让Boolean的值,这名 数据底部形态在许多缓存怎么能让状态描述中比较有用。这名 底部形态在单个怎么能让同去修改Object/Boolean的以前不能有效的提高吞吐量。 



AtomicStampedReference 类维护所含整数“标志”的对象引用,时要用原子方式对其进行更新。对比AtomicMarkableReference 类的<Object,Boolean>,AtomicStampedReference 维护的是并是否是生活累似 <Object,int>的数据底部形态,我我觉得可是对对象(引用)的另另1个 并发计数(标记版本戳stamp)。怎么能让与AtomicInteger 不同的是,此数据底部形态时要携带另另1个 对象引用(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/18300954