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

  • 时间:
  • 浏览:0
  • 来源:十分时时彩_十分时时彩网投平台_线上十分时时彩投注平台

前言:

  • 在并发访问请况下,原因分析分析会突然出现脏读、不可重复读和幻读等读难题,为了应对那此难题,主流数据库都提供了锁机制,并引入了事务隔离级别的概念。数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务共同存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性。
  • 乐观并发控制(乐观锁)和悲观并发控制(悲观锁)是并发控制主要采用的技术手段。无论是悲观锁还是乐观锁,都是大伙定义出来的概念,可需要认为是一种生活 思想。人太好不仅仅是关系型数据库系统涵盖乐观锁和悲观锁的概念,像memcache、hibernate、tair等都是你是什么的概念。
  • 本文中也将深入分析一下乐观锁的实现机制,介绍那此是CAS、CAS的应用以及CAS所处的难题等。

并发控制

在计算机科学,有点硬是守护tcp连接设计、操作系统、多避免机和数据库等领域,并发控制(Concurrency control)是确保及时纠正由并发操作原因分析的错误的一种生活 机制。

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

现有两处火车票售票点,共同读取某一趟列车车票数据库中车票余额为 X。两处售票点共同卖出一张车票,共同修改余额为 X -1写回数据库,好多好多 就造成了实际卖出两张火车票而数据库中的记录却只少了一张。 产生有些 请况的原因分析是原因分析分析有另有一个 事务读入同一数据并共同修改,其涵盖另有一个 事务提交的结果破坏了好多好多 事务提交的结果,原因分析其数据的修改被丢失,破坏了事务的隔离性。并发控制要避免的好多好多 你是什么难题。

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

一、数据库的锁

当并发事务共同访问有另有一个 资源时,有原因分析分析原因分析数据不一致,有些需要一种生活 机制来将数据访问顺序化,以保证数据库数据的一致性。锁好多好多 其中的一种生活 机制。

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

锁的分类(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、悲观锁:假定会所处并发冲突,屏蔽一切原因分析分析违反数据完整版性的操作。悲观锁的实现,往往依靠底层提供的锁机制;悲观锁会原因分析其它所有需要锁的守护tcp连接挂起,等待英文持有锁的守护tcp连接释放锁。

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的守护tcp连接安全难题。

守护tcp连接安全:

众所周知,Java是多守护tcp连接的。有些,Java对多守护tcp连接的支持人太好是一把双刃剑。一旦涉及到多个守护tcp连接操作共享资源的请况时,避免不好就原因分析分析产生守护tcp连接安全难题。守护tcp连接安全性原因分析分析是非常错综复杂的,在这麼充裕的同步的请况下,多个守护tcp连接中的操作执行顺序是不可预测的。

Java上边进行多守护tcp连接通信的主要最好的办法好多好多 共享内存的最好的办法,共享内存主要的关注点有有另有一个 :可见性和有序性。添加复合操作的原子性,大伙可需要认为Java的守护tcp连接安全性难题主要关注点有十个 :可见性、有序性和原子性。

Java内存模型(JMM)避免了可见性和有序性的难题,而锁避免了原子性的难题。这里不再完整版介绍JMM及锁的有些相关知识。有些大伙要讨论有另有一个 难题,那好多好多 锁到底是都是有利无弊的?

3.1 锁所处的难题

Java在JDK1.5之前 都是靠synchronized关键字保证同步的,有些 通过使用一致的锁定协议来协调对共享请况的访问,可需要确保无论哪个守护tcp连接持有共享变量的锁,都采用独占的最好的办法来访问那此变量。独占锁人太好好多好多 一种生活 悲观锁,好多好多 可需要说synchronized是悲观锁。

悲观锁机制所处以下难题:

1) 在多守护tcp连接竞争下,加锁、释放锁会原因分析比较多的上下文切换和调度延时,引起性能难题。

2) 有另有一个 守护tcp连接持有锁会原因分析其它所有需要此锁的守护tcp连接挂起。

3) 原因分析分析有另有一个 优先级高的守护tcp连接等待英文有另有一个 优先级低的守护tcp连接释放锁会原因分析优先级倒置,引起性能风险。

而好多好多 更加有效的锁好多好多 乐观锁。所谓乐观锁好多好多 ,每次不加锁好多好多 假设这麼冲突而去完成某项操作,原因分析分析原因分析分析冲突失败就重试,直到成功为止。

与锁相比,volatile变量是有另有一个 更轻量级的同步机制,原因分析分析在使用那此变量时可以 所处上下文切换和守护tcp连接调度等操作,有些volatile只能避免原子性难题,有些当有另有一个 变量依赖旧值时就只能使用volatile变量。有些对于同步最终还是要回到锁机制上来。

乐观锁

乐观锁( Optimistic Locking)人太好是一种生活 思想。相对悲观锁而言,乐观锁假设认为数据一般请况下可以 造成冲突,好多好多 在数据进行提交更新的之前 ,才会正式对数据的冲突是否进行检测,原因分析分析发现冲突了,则让返回用户错误的信息,让用户决定怎样去做。

上边提到的乐观锁的概念中人太好原因分析分析阐述了他的具体实现细节:

主要好多好多 有另有一个 步骤:冲突检测数据更新

人太好现最好的办法有一种生活 比较典型的好多好多 Compare and Swap(CAS)。

3.2 CAS

CAS是项乐观锁技术,当多个守护tcp连接尝试使用CAS共同更新同有另有一个 变量时,只能其涵盖另有一个 守护tcp连接能更新变量的值,而其它守护tcp连接都失败,失败的守护tcp连接暂且会被挂起,好多好多 被告知这次竞争中失败,并可需要再次尝试。

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提供了特殊的指令,允许算法执行读-修改-写操作,而可以 害怕有些守护tcp连接共同修改变量,原因分析分析原因分析分析有些守护tcp连接修改变量,这麼CAS会检测它(并失败),算法可需要对该操作重新计算。而 compareAndSet() 就用那此代替了锁定。

大伙以java.util.concurrent中的AtomicInteger为例,看一下在这麼锁的请况下是怎样保证守护tcp连接安全的。主要理解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原语,保证守护tcp连接间的数据是可见的(共享的)。好多好多 在获取变量的值的过可以够直接读取。有些来看看++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算法实现有另有一个 重要前提需要取出内存中某时刻的数据,而在下时刻比较并替换,这麼在有些 时间差类会原因分析数据的变化。

比如说有另有一个 守护tcp连接one从内存位置V中取出A,这之前 好多好多 守护tcp连接two也从内存中取出A,有些two进行了有些操作变成了B,有些two又将V位置的数据变成A,这之前 守护tcp连接one进行CAS操作发现内存中仍然是A,有些one操作成功。尽管守护tcp连接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/1860 954