[数据库锁机制] 深入理解乐观锁、悲观锁以及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/1880954