欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

数据库---通俗理解乐观锁和悲观锁

程序员文章站 2022-06-02 08:56:43
...

mysql数据库和java中有关乐观锁和悲观锁的概念以及实现方式

在数据库的锁机制中,数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时在存储数据库中同一数据时不破坏事物的隔离性和统一性以及数据库的统一性.

乐观并发控制(乐观锁)和悲观并发控制(悲观锁)是在并发操作时控制数据库的主要手段.

但是无论是悲观锁还是乐观锁,都是人们定义出来概念,可以认为是一种思想.其实不仅仅是关系型数据系统中有乐观锁和悲观锁的概念,像memcache,hibernate,tair等有类似的概念.

悲观锁:一段执行逻辑加上悲观锁,不同线程同时执行时,只能有一个线程执行,其他的线程在入口处等待,直到锁被释放.

乐观锁:一段执行逻辑加上乐观锁,不同线程同时执行时,可以同时进入执行,在最后更新数据的时候要检查这些数据是否被其他线程修改了(版本和执行初是否相同),没有修改则进行更新,否则放弃本次操作.

乐观锁

在关系数据库管理系统里,乐观并发控制(又名”乐观锁”,Optimistic Concurrency Control,缩写”OCC”)是一种并发控制的方法。它假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自影响的 那部分数据。在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其他事务有更新的话,正在提交的事务会进行回 滚。乐观事务控制最早是由孔祥重(H.T.Kung)教授提出。

乐观并发控制的事务包括以下阶段: 
1. 读取:事务将数据读入缓存,这时系统会给事务分派一个时间戳。 
2. 校验:事务执行完毕后,进行提交。这时同步校验所有事务,如果事务所读取的数据在读取之后又被其他事务修改,则产生冲突,事务被中断回滚。 
3. 写入:通过校验阶段后,将更新的数据写入数据库。

乐观并发控制多数用于数据争用不大、冲突较少的环境中,这种环境中,偶尔回滚事务的成本会低于读取数据时锁定数据的成本,因此可以获得比其他并发控制方法更高的吞吐量。相对于悲观锁,在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制。

一种的实现乐观锁的方式就是记录数据版本

数据版本,为数据增加的一个版本标识。当读取数据时,将版本标识的值一同读出,数据每更新一次,同时对版本标识进行更新。当我们提交更新的时候,判 断数据库表对应记录的当前版本信息与第一次取出来的版本标识进行比对,如果数据库表当前版本号与第一次取出来的版本标识值相等,则予以更新,否则认为是过 期数据。

实现数据版本有两种方式,第一种是使用版本号,第二种是使用时间戳。 使用版本号实现乐观锁

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

另一种实现乐观锁的方式就是CAS

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

   CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前值。)CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。 ”这其实和乐观锁的冲突检查+数据更新的原理是一样的。

优点和不足:

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

悲观锁

在关系数据库管理系统里,悲观并发控制(又名”悲观锁”,Pessimistic Concurrency Control,缩写”PCC”)是一种并发控制的方法。它可以阻止一个事务以影响其他用户的方式来修改数据。如果一个事务执行的操作读某行数据应用了 锁,那只有当这个事务把锁释放,其他事务才能够执行与该锁冲突的操作。

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

使用

MySQL InnoDB中使用悲观锁(在java中可加Synchronized关键字实现)

要使用悲观锁,我们必须关闭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语句用不到索引是不会使用行级锁的,会使用表级锁把整张表锁住,这点需要注意。

优点与不足

悲观并发控制实际上是”先取锁再访问”的保守策略,为数据处理的安全提供了保证。但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增 加产生死锁的机会;另外,在只读型事务处理中由于不会产生冲突,也没必要使用锁,这样做只能增加系统负载;还有会降低了并行性,一个事务如果锁定了某行数 据,其他事务就必须等待该事务处理完才可以处理那行数.

乐观锁和悲观锁实现方式CAS和Synchronized的对比:

CAS与Synchronized的使用情景:   

    1、对于资源竞争较少(线程冲突较轻)的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。

    2、对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。

   补充: synchronized在jdk1.6之后,已经改进优化。synchronized的底层实现主要依靠Lock-Free的队列,基本思路是自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS。

 

共享锁

  共享锁指的就是对于多个不同的事务,对同一个资源共享同一个锁。相当于对于同一把门,它拥有多个钥匙一样。就像这样,你家有一个大门,大门的钥匙有好几把,你有一把,你女朋友有一把,你们都可能通过这把钥匙进入你们家,进去啪啪啪啥的,一下理解了哈,没错,这个就是所谓的共享锁。

  刚刚说了,对于悲观锁,一般数据库已经实现了,共享锁也属于悲观锁的一种,那么共享锁在mysql中是通过什么命令来调用呢。通过查询资料,了解到通过在执行语句后面加上lock in share mode就代表对某些资源加上共享锁了。

排它锁

排它锁与共享锁相对应,就是指对于多个不同的事务,对同一个资源只能有一把锁。

与共享锁类型,在需要执行的语句后面加上for update就可以了

行锁

行锁,由字面意思理解,就是给某一行加上锁,也就是一条记录加上锁。

比如之前演示的共享锁语句

SELECT * from city where id = "1"  lock in share mode; 

由于对于city表中,id字段为主键,就也相当于索引。执行加锁时,会将id这个索引为1的记录加上锁,那么这个锁就是行锁。

表锁

表锁,和行锁相对应,给这个表加上锁。