面试必备-行锁、表锁 - 乐观锁、悲观锁的区别和联系(史上最全)

一、相关名词

|–表级锁(锁定整个表)

|–页级锁(锁定一页)

|–行级锁(锁定一行)

|–共享锁(S锁,MyISAM 叫做读锁)

|–排他锁(X锁,MyISAM 叫做写锁)

|–意向共享锁(IS锁,事务打算给数据行加共享锁,但是在这之前必须获得意向共享锁,innodb引擎会自动给符合条件的事务加IS锁)

|–意向排他锁(IX锁,事务打算给数据行加排他锁,但是在这之前必须获得意向排他锁,innodb引擎会自动给符合条件的事务加IX锁)

|–悲观锁(抽象性,不真实存在这个锁)

|–乐观锁(抽象性,不真实存在这个锁)

1 排他锁/共享锁

1.1 共享锁(Shared Lock)

也称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是都只能读不能修改,因为当多个事务对同一行数据执行修改操作时,会产生死锁现象。

1.2 排他锁(Exclusive Lock)

  • 也称X锁,获取排他锁的事务可以对数据行读取和修改;如果一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的共享锁和排他锁,

  • 对于共享锁大家可能很好理解,就是多个事务只能读数据不能改数据,对于排他锁大家的理解可能就有些差别,这里有一个重点就是:排他锁锁住一行数据后,其他事务就不能读取和修改该行数据,其实不是这样的。排他锁指的是一个事务在一行数据加上排他锁后,其他事务不能再在该行上上加其他的锁。mysql InnoDB引擎默认的修改数据语句,update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型,如果加排他锁可以使用select …for update语句,加共享锁可以使用select … lock in share mode语句。所以加过排他锁的数据行在其他事务种是不能修改数据的,也不能通过for update和lock in share mode锁的方式查询数据,但可以直接通过select …from…查询数据,因为普通查询没有任何锁机制

  • 申请排他锁的前提是,没有线程对该结果集的任何行数据使用排它锁或者共享锁,否则申请会受到阻塞。

SELECT... FOR UPDATE使用注意事项:

1, for update仅适用于innodb,且必须在事务范围内才能生效。

2, 申请排他锁的前提是,没有线程对该结果集的任何行数据使用排它锁或者共享锁,否则申请会受到阻塞。

3, 根据主键进行查询,查询条件为like或者不等于,主键字段产生表锁。

4, 根据非索引字段进行查询,会产生表锁。

2 悲观锁/乐观锁锁

2.1 悲观锁

2.2.1 定义:

总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁/表锁等,读锁/写锁,共享锁/排他锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
or
总是认为多个事务/用户访问数据库时会产生并发冲突,因此需要消除所有可能会产生冲突的操作

2.1.2 策略

悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会。另外还会降低并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数据。

Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
2.1.3 以 MySql Innodb 引擎举例,说明 SQL 中悲观锁的应用

要使用悲观锁,必须关闭 MySQL 数据库的自动提交属性set autocommit=0。因为 MySQL 默认使用 autocommit 模式,也就是说,当执行一个更新操作后,MySQL 会立刻将结果进行提交[因为一个事务内可能有多条sql语句,如果只执行完其中一条就立即提交,可能会产生脏读幻读不可重复读的问题]。

以电商下单扣减库存的过程说明一下悲观锁的使用:
在这里插入图片描述
在对 id = 1 的记录修改前,先通过 for update的方式进行加锁,然后再进行修改。这就是比较典型的悲观锁策略。

如果发生并发,同一时间只有一个线程可以开启事务并获得 id=1 的锁,其它的事务必须等本次事务提交之后才能执行。这样可以保证当前的数据不会被其它事务修改。

2.1.4 悲观锁的典型代表 - X锁和S锁的使用策略

1.共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务获取相同数据集的共享读锁和排他写锁。
另外,为了允许行锁和表锁共存,事项多粒度锁机制,innodb 还有两种内部使用的意向锁,这两种意向锁都是表锁

2.意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的 IS 锁。
意向排它锁(IX): 事务打算给数据行加行排它锁,事务在给一个数据行加排它锁前必须先取得该表的 IX 锁。

3.S和X锁使用的策略

如果一个事务请求的锁模式与当前的锁兼容,innodb 就将请求的锁授予该事务;反之,如果两者不兼容,该事务就要等待锁释放。
意向锁是 innodb 自动加的,不需要用户干预。对于 update、delete 和 insert 语句,innodb 会自动给涉及数据集加排它锁(X);对于普通 select 语句,innodb 不会加任何锁。
事务可以通过以下语句显式给记录集加共享锁或排它锁。
共享锁(S):select * from table_name where … lock in share mode.
排它锁(X): select * from table_name where … for update.
用 select… in share mode 获得共享锁,主要用在需要数据依存关系时来确认某行记录是否存在,并确保没有人对这个记录进行 update 或者 delete 操作。但是如果当前事务也需要对该记录进行更新操作,则有可能造成死锁,对于锁定行记录后需要进行更新操作的应用,应该使用 select… for update 方式获得排他锁。

4.X锁和S锁的兼容性问题
https://img-blog.csdnimg.cn/20210403115035130.png
也就是说,当事务拥有数据A排他锁时,其他数据库连接是无法获取数据A的共享、排他锁的;当事务拥有数据A共享锁时,其他数据库连接无法获取数据A的排他锁,可以获取到共享锁。
那么问题来了,若有一个事务A得到了共享锁,此时B一个update请求在等待锁释放,另一个请求C又想获取共享锁,那么C此时可以获取到共享锁吗?答案当然是不可以,因为B要执行更新,自然会获取数据的意向排他锁,当拿到IX锁后,其他的请求想要去获取意向共享锁,因为B已经上了意向排他锁,自然是要在B执行更新之后才可以。

5.那么若使用共享锁,执行更新,如何产生死锁呢:

开启两个会话窗口,都开启事务(不开启事务的话,执行select lock in share mode;就直接释放共享锁了),都执行了select * from user lock in share mode;此时两个事务,都有这两条数据的共享锁,此时无法执行更新这两条数据的sql,需要等待其他拥有共享锁的事务释放锁,左侧先调用update,右侧再调用update,因为两个都拥有着共享锁,且都等待对方释放锁,故就会出现死锁现象。
https://img-blog.csdnimg.cn/20210403114058325.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tvbGJqYmU=,size_16,color_FFFFFF,t_70

2.1.5 使用几种排他锁 - 记录锁,间隙锁和临键锁 来解决不可重复读的问题
三种锁的说明及其使用
  1. 记录锁(Record Locks)

记录锁是 封锁记录,记录锁也叫行锁,例如:
SELECT * FROMtestWHEREid=1 FOR UPDATE;
它会在 id=1 的记录上加上记录锁,以阻止其他事务插入,更新,删除 id=1 这一行。
记录锁、间隙锁、临键锁都是排它锁

  1. 间隙锁(Gap Locks)

间隙锁是封锁索引记录中的间隔(是一个左开右闭的区间,即(x,y]的形式,这个区间里面的所有出现的记录也会被加锁),或者第一条索引记录之前的范围(包含第一条记录),又或者最后一条索引记录之后的范围(不包含最后一条记录)。

产生间隙锁的条件(RR事务隔离级别下;):

  • 使用普通索引锁定,因为索引的值不唯一,这个时候会产生多个匹配的值;
  • 使用多列唯一索引;
  • 使用唯一索引锁定多行记录,因为唯一索引允许字段中多个值为空的情况,就会锁定多行记录。
  • 使用范围查询等情况,都会产生间隙锁
  1. 临键锁(Next-key Locks)

临键锁:是记录锁与间隙锁的组合,命中时候,它的封锁范围,既包含索引记录,又包含索引区间。
临键锁针对的是普通索引(注意不是唯一索引),没命中数据,锁间隙,命中数据,锁住数据及数据所在的间隙范围

  1. 在sql语句中使用以上三种排他锁的方式model: select * from tableName for update; 在数据库的优化器优化过程中会根据where字段的用到的索引类别,自动的加相应种类的排他锁。
三种锁的应用实战

数据:
https://img-blog.csdnimg.cn/20210403145038166.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tvbGJqYmU=,size_16,color_FFFFFF,t_70

  1. 先看主键加锁:

主键索引是唯一索引,所以当命中数据后,临键锁会退化为行锁,右侧执行update需要等待锁的释放
https://img-blog.csdnimg.cn/20210403145302547.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tvbGJqYmU=,size_16,color_FFFFFF,t_70
当未命中到数据时,临键锁会退化为间隙锁(这个时候根据的不管是普通索引查询还是唯一索引查询,只要未命中,主键锁会退化为间隙锁),左侧查询对id为15的数据加锁,因为没有该条数据,RR下,会锁住15所在的区间范围,因为存在的数据有1、2、3、10、20,30,15处在(10,20)范围内,故间隙锁会锁住(10,20)这个范围,右侧插入id为13的数据也要等待,左侧锁释放才能执行,否则就会超时异常
https://img-blog.csdnimg.cn/20210403150521673.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tvbGJqYmU=,size_16,color_FFFFFF,t_70
若想查询加锁的是id=40呢,锁住的范围就是(30,max),所以可重复读的锁的范围还是很大的,这也是容易造成死锁的原因。

但是当对范围进行查询的时候,

2. 再看针对唯一索引的加锁()

接下来测试唯一索引加锁(应该和主键索引的效果一样,因为主键索引也是唯一索引),而数据库操作引擎判断for update是临键锁还是间隙锁的依据就是唯一索引,参考上一条的案例就行

3. 最后查看普通索引情况下的加锁情况

数据变更:
https://img-blog.csdnimg.cn/20210403160732428.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tvbGJqYmU=,size_16,color_FFFFFF,t_70

对不存在的aba数据加锁,从右侧sql可以看出,只是对(aa,abc)的区间进行加锁了(间隙锁)。
https://img-blog.csdnimg.cn/2021040316122378.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tvbGJqYmU=,size_16,color_FFFFFF,t_70

对存在的abb加锁,从右侧可以看出列name是普通索引,所以会采用临键锁的方式加锁,锁住(aa,abb]区间,故右侧sql执行插入name为aba的数据,锁会超时。
https://img-blog.csdnimg.cn/2021040316383067.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tvbGJqYmU=,size_16,color_FFFFFF,t_70

4. 对非索引字段加锁的情况

左侧开启事务,对money为200的数据进行加锁,然后我们看下右侧sql执行的情况,无论是更新锁住这条数据,还是插入money(200,300)、(0,200)、(200,1000)都无法拿到锁,所以当where对非索引字段for update时,会直接锁表。
https://img-blog.csdnimg.cn/20210403152105228.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tvbGJqYmU=,size_16,color_FFFFFF,t_70

可重复读给出了这么多的锁目的就是来解决幻读的问题,为了防止多个事务把记录插入到同一范围中去,但是在可重复读的隔离级别下仍然还是会有一定的幻读问题。

2.2 乐观锁

2.2.1 定义

总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
or
总是认为多用户访问不会产生并发冲突,只需要在用户提交事务之前校验一下其完整性

2.2.2 策略

乐观并发控制相信事务之间的数据竞争(data race)的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁。

2.2.3 乐观锁的实现方法
2.2.3.1版本号机制

一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
or
也就是说在事务提交之前总共读取两次version,第一次是执行update/insert/delete等操作之前读取一次,第二次是在执行完update/insert/delete这些但是在即将提交事务之前读取,如果两次的version值没有被修改,则提交事务,完成更新

e.g:
假设数据库中帐户信息表中有一个 version 字段,当前值为 1 ;而当前帐户余额字段( balance )为 $100 。当需要对账户信息表进行更新的时候,需要首先读取version字段。

  1. 操作员 A 此时将其读出( version=1 ),并从其帐户余额中扣除 $50( $100-$50 )。
  2. 在操作员 A 操作的过程中,操作员B 也读入此用户信息( version=1 ),并从其帐户余额中扣除 $20 ( $100-$20 )。
  3. 操作员 A 完成了修改工作,提交更新之前会先看数据库的版本和自己读取到的版本是否一致,一致的话,就会将数据版本号加1( version=2 ),连同帐户扣除后余额( balance=$50 ),提交至数据库更新,此时由于提交数据版本大于数据库记录当前版本,数据被更新,数据库记录 version 更新为 2
  4. 操作员 B 完成了操作,提交更新之前会先看数据库的版本和自己读取到的版本是否一致,但此时比对数据库记录版本时发现,操作员 B 提交的数据版本号为 2 ,而自己读取到的版本号为1 ,不满足 “ 当前最后更新的version与操作员第一次读取的版本号相等 “ 的乐观锁策略,因此,操作员 B 的提交被驳回。
  5. 这样,就避免了操作员 B 用基于 version=1 的旧数据修改的结果覆盖操作员A 的操作结果的可能。
2.2.3.2 CAS(compare and swap)算法

1,定义:

CAS 即比较并交换。是解决多线程并行情况下使用带版本号机制的锁造成性能损耗的一种机制,CAS 操作包含三个操作数——内存位置值(V)、预期原值(A)和新值(B)。

2,过程:

  • 在事务开始的时候,线程会从内存中首次取出V,并且令A = V,
  • 当事务提交之前,该线程第二次从内存取出V,此时如果A == V为true[即如果内存位置的值(V)与预期原值(A)相匹配,即内存中的值没有被其他事务修改过],那么处理器会自动将该位置值更新为新值(B)。否则,处理器不做任何操作。但是更新失败的线程仍然会不断做自旋操作(不断尝试更新数据)

3,为什么要用CAS机制?

(1)因为数据库中的悲观锁机制(Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现;此外排他锁/共享锁,行锁/表锁等锁机制也是属于悲观锁范畴)大大的降低了数据库事务的并发度,不利于提升效率[synchronized锁每次只会让一个线程去操作共享资源]

(2)CAS实际上是一种不加锁的机制,多个线程都能直接操作共享资源,只有在提交的时候才判断是否允许修改,这样的话并发度很高

4,CAS的缺点 - ABA问题
问题描述:

线程A在事务开始时取出的内存值V = 10,并且令A = V, 在执行过程,线程B对这个V值修改为100,随后线程C又将其修改为10,当事务A要提交前又访问了一次内存第二次取出V值时,发现V = A,这个时候线程A认为这个数据没有被修改,因此把事务给提交了。

5,ABA问题的解决方案:

  • 可以加一个版本字段(在内存中CAS数据结构中添加这个字段),因此在比较的时候,既需要比较内存中的数据值,也需要比较版本值,只有两者都没改变的时候,才允许事务提交。

  • 具体的实现过程:
    乐观锁每次在执行数据修改操作时,都会带上一个版本号,一旦版本号和数据的版本号一致就可以执行修改操作并对版本号执行 +1 操作,否则就执行失败。因为每次操作的版本号都会随之增加,所以不会出现 ABA 问题。除了 version自增 以外,还可以使用时间戳,因为时间戳天然具有顺序递增性。

6,优化后产生的问题:

以上 SQL 其实还是有一定的问题的,就是一旦遇上高并发的时候,就只有一个线程可以修改成功,那么就会存在大量的失败。对于像淘宝这样的电商网站,高并发是常有的事,总让用户感知到失败显然是不合理的。
(1)解决方案一

所以,还是要想办法减少乐观锁的粒度。一个比较好的建议,就是减小乐观锁力度,最大程度的提升吞吐率,提高并发能力!如下

https://img-blog.csdnimg.cn/f21643ec606e4358b9d432f185a05d93.png

降低锁粒度
以上 SQL 语句中,如果用户下单数为 1,则通过quantity - 1 > 0的方式进行乐观锁控制。在执行过程中,会在一次原子操作中查询一遍 quantity 的值,并将其扣减掉 1。

(2)解决方案二:LongAdder和AtomicLong

1)阿里巴巴开发手册提及到 推荐使用 LongAdder(能大大减少乐观锁的重试次数) 对象,比 AtomicLong 性能更好,你能帮我解读一下吗?

2)使用AtomicLong时,所有线程把一条含有多个分量的数据当成一个目标,一旦获得对这条数据的控制权,其他的线程都只能执行自旋操作。因此在高并发时,只有一个线程是执行成功的,其他的线程都会失败,不断自旋(重试),自旋会成为瓶颈
3)而LongAdder类允许对这一条数据进行切割,将不同的分量分配给不同的线程,这样事务的回滚概率大大降低。
总的来说就是而LongAdder的思想就是把要操作的目标资源「分散」到数组Cell中,每个线程对自己的 Cell 变量的 value 进行原子操作,大大降低了失败的次数

2.2.3.3 CAS和版本号机制的异同

相似之处:

总的来说,CAS和上面提到的版本号具体的过程有一定类似;而且这两种方式实现的乐观锁不需要借助数据库的锁机制

不同之处:

版本号机制需要数据库的配合,但是CAS机制是由硬件完成的一系列原子操作。

2.2.4 乐观锁的版本号机制和MVCC机制有什么异同?

相同点:

  • 两者都基于乐观锁的理论而实现的
  • 两者的目的都是为了最大限度地提升数据库的并发度

不同点:(待定)

乐观锁和mvcc就是一条update语句的更新流程的前后脚的关系
数据更新之前需要选择一种锁机制乐观锁比如版本号机制,也可以用悲观锁比如排他锁,用了锁之后,update类型的事务才会开始执行,然后如果事务发现执行不成功需要回滚,会用到mvcc机制的快照功能恢复数据,从而保证了事务的原子性操作,最后不管提交与否都会释放锁。

以采用乐观锁的版本号机制为例:(待定)
比如事务A需要更新记录B,并且采用版本号机制实现乐观锁,那么他首先会从本地缓存中或者数据库中取出该条记录,从取出的记录中拿到版本号字段version的值单独保存到内存中,并且将记录中的version字段加1,随后开始更新记录B,更新完成后准备提交事务之前再从数据库中取出一次version,查看version字段是否被更改,如果没有被更改则提交事务(也就是将数据写回磁盘块中),如果被更改,则更新不成功,利用MVCC机制进行事务回滚,恢复修改前的数据的。

如何选择锁

在乐观锁与悲观锁的选择上面,主要看下两者的区别以及适用场景就可以了。

1️⃣响应效率:如果需要非常高的响应速度,建议采用乐观锁方案,成功就执行,不成功就失败,不需要等待其他并发去释放锁。乐观锁并未真正加锁,效率高。一旦悲观锁的粒度掌握不好,更新失败的概率就会比较高,容易发生业务失败。

2️⃣冲突频率:如果冲突频率非常高,建议采用悲观锁,保证成功率。冲突频率大,选择乐观锁会需要多次重试才能成功,代价比较大。

3️⃣重试代价:如果重试代价大,建议采用悲观锁。悲观锁依赖数据库锁,效率低。更新失败的概率比较低。

4️⃣乐观锁如果有人在你之前更新了,你的更新应当是被拒绝的,可以让用户重新操作。悲观锁则会等待前一个更新完成。这也是区别。

随着互联网三高架构(高并发、高性能、高可用)的提出,悲观锁已经越来越少的被应用到生产环境中了,尤其是并发量比较大的业务场景

锁的种类划分与联系,更具体文章可参考点我

在这里插入图片描述
在这里插入图片描述

2 行锁与表锁

2.1 行锁

传统的关系型数据库里边就用到了悲观锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。就像for update,再比如Java里面的同步原语synchronized关键字的实现也是悲观锁。

3 以上关于各种锁的详细说明,请参考另一篇博客

深入理解数据库行锁与表锁

3.1 对上面博客的补充:

插入意向锁:插入意向锁定是在行插入之前由INSERT操作设置的一种间隙锁。这个锁表示插入的意图,即插入相同索引间隙的多个事务如果不插入间隙内的相同位置则不需要等待彼此,插入意向锁是一种特殊的GAP LOCK。

2.3 表锁:意向锁 Intention Locks,意向锁相互兼容
1、表明“某个事务正在某些行持有了锁、或该事务准备去持有锁”

2、意向锁的存在是为了协调行锁和表锁的关系,支持多粒度(表锁与行锁)的锁并存,。

3、例子:事务A修改user表的记录r,会给记录r上一把行级的排他锁(X),同时会给user表上一把意向排他锁(IX),这时事务B要给user表上一个表级的排他锁就会被阻塞。意向锁通过这种方式实现了行锁和表锁共存且满足事务隔离性的要求。

4、1)意向共享锁(IS锁):事务在请求S锁前,要先获得IS锁
2)意向排他锁(IX锁):事务在请求X锁前,要先获得IX锁

q1:为什么意向锁是表级锁呢?
当我们需要加一个排他锁时,需要根据意向锁去判断表中有没有数据行被锁定(行锁);

(1)如果意向锁是行锁,则需要遍历每一行数据去确认;

(2)如果意向锁是表锁,则只需要判断一次即可知道有没数据行被锁定,提升性能。

q2:意向锁怎么支持表锁和行锁并存?

(1)首先明确并存的概念是指数据库同时支持表、行锁,而不是任何情况都支持一个表中同时有一个事务A持有行锁、又有一个事务B持有表锁,因为表一旦被上了一个表级的写锁,肯定不能再上一个行级的锁。

(2)如果事务A对某一行上锁,其他事务就不可能修改这一行。这与“事务B锁住整个表就能修改表中的任意一行”形成了冲突。所以,没有意向锁的时候,让行锁与表锁共存,就会带来很多问题。于是有了意向锁的出现,如q1的答案中,数据库不需要在检查每一行数据是否有锁,而是直接判断一次意向锁是否存在即可,能提升很多性能。

  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
乐观锁悲观锁是在并发编程中用于处理数据一致性的两种不同的策略。 乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候。它通过在读取数据的时候不加,而是在更新数据的时候进行比较并重试的方式来保证数据的一致性。乐观锁的优点是可以省去的开销,从而加大系统的整个吞吐量。但是如果冲突发生的频率较高,上层应用会不断进行重试,这样会降低性能,此时悲观锁更合适。 悲观锁则相反,它认为冲突会经常发生,因此在读取和更新数据时都会加悲观锁的优点是能够确保数据的一致性,但是缺点是增加了的开销,降低了系统的整体吞吐量。所以在冲突频率高的情况下,使用悲观锁效果更好。 乐观锁的一个常见问题是CAS操作会产生ABA问题。ABA问题指的是一个值在经过多次更新之后又回到了原来的值,导致无法感知到中间的值变化。为了解决ABA问题,可以使用版本号或时间戳来辅助判断数据是否发生变化。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [悲观锁乐观锁的实现(详情图解)含面试专题及答案](https://blog.csdn.net/m0_67322837/article/details/124039600)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值