MySQL~InnoDB引擎解决脏读,不可重复读,幻读,丢失更新的原理(lock事务锁、自增长锁、Record Lock、Gap Lock、Next-Key Lock、死锁)

本文详细解读了Java中InnoDB的行锁机制、事务的两段锁协议、不同隔离级别的工作原理,以及如何处理死锁。重点介绍了Next-KeyLock和幻读的概念,并提到并发控制和死锁解决策略。
摘要由CSDN通过智能技术生成

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!

  • 由于InnoDB支持的是行级别的锁,因此意向锁不会阻塞除全表扫描外的任何请求,故兼容性如下。

在这里插入图片描述

事务中的行锁机制


  • 事务的几种性质原子性,持久性,隔离性,一致性,数据库为了维护这些性质,尤其是一致性和隔离性,一般使用加锁这种方式。同时数据库又是个高并发的应用,同一时间会有大量的并发访问,如果加锁过度,会极大的降低并发处理能力。所以对于加锁的处理,可以说就是数据库对于事务处理的精髓所在。

俩段锁

  • 在事务开始阶段,数据库并不知道会用到哪些数据。数据库遵循的是两段锁协议,也就是将事务分成两个阶段,加锁阶段和解锁阶段(所以叫两段锁)

  • 加锁阶段:在该阶段可以进行加锁操作。在对任何数据进行读操作之前要申请并获得S锁(共享锁,其它事务可以继续加共享锁,但不能加排它锁),在进行写操作之前要申请并获得X锁(排它锁,其它事务不能再获得任何锁)。加锁不成功,则事务进入等待状态,直到加锁成功才继续执行。

  • 解锁阶段:当事务释放了一个封锁以后,事务进入解锁阶段,在该阶段只能进行解锁操作不能再进行加锁操作。

在这里插入图片描述

  • 这种方式虽然无法避免死锁.死锁的解决办法在后面会讲到,但是两段锁协议可以保证事务的并发调度是串行化(串行化很重要,尤其是在数据恢复和备份的时候)的。

行锁的三个实现算法

  • 在InnoDB中由三种行锁的算法
  1. Record Lock:单个行记录的上锁

  2. Gap Lock:间歇锁,不包含记录本身的区间锁

  3. Next-Key Lock:包含记录本身的区间锁, 是前两个算法的结合体

  • 为了更好的理解他们的区别,我拿一组数据来进行举例

有一组数据,其索引分别为10、30、60

此时使用SQL语句SELECT * FROM t WHERE id = 10 FOR UPDATE,三种锁的范围如下

Record Lock: 对10单行进行加锁

Gap Lock : (-∞, 10)、(10, 30)、(30, 60)、(60, +∞)

Next-Key Lock : (-∞,10]、(10,30]、(30,60]、(60,+∞)

对于Record Lockl来说,其总是会去锁住索引记录,即使没有设置任何一个索引,它也会使用隐式的索引进行锁定。

Next-Key Lock是结合了前面所说的两种锁算法,既锁住范围,也锁住记录本身,在InnoDB中对于行的查询都会采用这种算法,而设计它的目的正是为了解决幻读问题。

事务的四种隔离级别

  • 在数据库操作中,为了有效保证并发读取数据的正确性,提出的事务隔离级别。我们的数据库行锁,也是为了构建这些隔离级别存在的。

在这里插入图片描述

  • 未提交读(Read Uncommitted):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据

  • 提交读(Read Committed):只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别 (不重复读)

  • 可重复读(Repeated Read):可重复读。在同一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻象读

  • 串行读(Serializable):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞

实现原理

Read UnCommitted(读未提交)
  • Read Uncommitted这种级别,数据库一般都不会用,而且任何操作都不会加锁,这里就不讨论了。
Read Committed(读提交)
  • 在RC级别中,数据的读取都是不加锁的,但是数据的写入、修改和删除是需要加锁的。

  • 也就是在事务中执行insert update 和delete时会加锁,但是执行select语句的时候是不会加锁的

  • 那对于上锁, 在RC级别中使用的锁机制是Record Lock:单个行记录的上锁,

  • mysql的innoDB引擎的行锁机制锁的是索引,不是行记录,所以如果没有使用索引,那么会使用表锁,直接锁住整张表

Repeatable Read(可重复读)
  • 这是MySQL中InnoDB默认的隔离级别。

  • 他主要致力于解决不可重复读问题, 就是在一个事务中执行同一条sql俩次, 但是得到的数据信息是不一样的, 就类似下面情况

  • 事务A进行了一次查询id=1, 但是事务并没有提交, 事务B修改id=1的数据提交之后,事务A同样的查询,后一次和前一次的结果不一样,这就是不可重读(重新读取产生的结果不一样)。

  • 产生的原因就是没有给读加锁, 在Read Committed(读提交)的隔离机制中,只对update和insert,delete加锁,而对select读操作未进行加锁,所以当A事务读取值x=100,但此时B事务对x修改为x=200,导致A事务再次读x时为200

  • 在可重复读的隔离机制中,会对select,update,insert,delete加锁, 但是还是有问题,另外一个事务又在该范围内insert插入了新的记录,当之前的事务再次读取该范围时,会产生幻读(符合条件的记录增多了)

  • 在可重复读中,该sql第一次读取到数据后,就将这些数据加锁,其它事务无法修改这些数据,就可以实现可重复读了。但这种方法却无法锁住insert的数据,所以当事务A先前读取了数据,或者修改了全部数据,事务B还是可以insert数据提交,这时事务A就会发现莫名其妙多了一条之前没有的数据,这就是幻读,不能通过行锁来避免.

  • 所以当某个事务读取某个范围内的记录时,innodb存储引擎会对符合条件的记录行加上记录行之间的间隙进行上锁

  • 需要使用Next-Key + gap Lock锁(行锁与间隙锁的组合锁)解决幻读问题,当InnoDB扫描索引记录的时候,会首先对选中的索引记录加上行锁,再对索引记录两边的间隙上锁,那么这样相当于只要在符合where查询的条件区间,不管是不是存在真实的记录,都会上锁,保证了当前事务,这整个区间是同步的。

  • 至于为什么是Next-Key + gap Lock锁是因为,对于主键的聚集索引来说, 使用的是Next-Key Lock机制, 将查询的索引和之间的间隙都锁住, gap lock是用来锁住辅助索引, 将辅助索引的上一条数据的间隙和下一条数据的间隙也锁住

  • 所以说不可重复读和幻读最大的区别,就在于如何通过锁机制来解决他们产生的问题, 不可重复读只需要给select加锁即可, 但是幻读问题必须是使用Next-Key + gap Lock锁将整个范围锁住。

  • 都知道使用锁自然会降低效率, 但是InnoDB还是想办法使用MVCC多版本并发控制提高了查询的效率, 这个后面讲

丢失更新
  • 丢失更新是一个事务的更新操作会被另一个事务的更新操作覆盖,比如下面案例

  • 在实现同一个用户在俩台设备上转账的时候, 一台设备启动一个事务A, 事务A的任务是转账9999, 另一台设备启动事务B, 事务B任务的转账1圆, 此时俩台设置同时启动, 事务A首先会查看用户的余额, 发现是10000元, 可以进行转账, 还未时间操作数据实现转账的时候, 由于上的是共享锁所以事务B也查看用户的余额, 还是一万元, 然后事务A的转账操作执行, 然后提交事务释放了排它锁, 事务B的转账一元, 然后提交事务, 此时数据库中的余额是多少?是9999, 因为事务A的转账更新丢失了, 到时数据出现错误

  • 要避免更新丢失的发生, 就必须让读与加一个排他锁, 或者最安全的就是让事务的执行完全串行化, 也就是让隔离级别达到串行化Serializable级别

Serializable (串行化)
  • 使用的悲观锁的理论给读和写都加表级别锁,实现简单,数据更加安全,但是并发能力非常差。

自增长锁


  • 自增长在数据库中很常见, 是主键的首选方式, 在InnoDB引擎中, 对每一个自增长的表都有一个自增长计数器, 当对自增长列进行插入操作的时候, 就会将这个自增长值进行加一, 但是在高并发下, 这个加一操作并不是安全的, 所以InnoDB也对其进行了上锁, 这个锁与X锁和S锁不一样, 不是在事务提交后进行释放,而是当一条插入sql执行完毕后就会释放

死锁


死锁指的是两个或者两个以上的事务在执行过程中,因为争抢所资源而导致的一种互相等待的现象。在死锁的情况下,如果没有外力作用,事务将永远无法推进下去。

在数据库中通常都会使用超时机制来解决死锁。即为事务设置超时时间,即使两个事务互相等待,当其中一方超时后立刻进行回滚,另一个事务就能够继续进行了。

  • 虽然超时机制可以解决这个问题,但是我们并不能掌握回滚的事务的量级,倘若事务更新庞大,则回滚就会带来大量的性能损耗,所以我们通常会采用更加主动的策略,即使用等待图来进行死锁检测

等待图主要有俩个信息链表, 一个是锁的信息链表, 一个事务等待链表

在这里插入图片描述

  • 由图可以发现, 在事务等待链表中有4个事务, 分别是t1~t4, 在row1行中的锁的信息链表表示t2事务上了X锁, t1事务上了S锁, 由上面俩个信息链表就可以构建出等待图

在这里插入图片描述

图中每个节点即为一个事务

每条指向其他节点的线则代表着正在等待该节点的资源
当存在回路时,则代表着事务互相等待,此时就意味着存在死锁

  • 每当事务请求锁并发生等待时,都会主动判断等待图中是否存在回路,如果存在则代表着有死锁产生,此时就会主动选择undo量最小的事务来打破死锁。在现版本的InnoDB中,通常采用深度优先搜索(老版本使用递归)来检测死锁的存在。发现有死锁就会查看当前事务的版本然后在undo.log中进行查找那个事务的回滚数据量小就会回滚哪个事务

总结

如果你选择了IT行业并坚定的走下去,这个方向肯定是没有一丝问题的,这是个高薪行业,但是高薪是凭自己的努力学习获取来的,这次我把P8大佬用过的一些学习笔记(pdf)都整理在本文中了

《Java中高级核心知识全面解析》

小米商场项目实战,别再担心面试没有实战项目:

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!
心知识全面解析》**

[外链图片转存中…(img-DNlGO30S-1714710988755)]

小米商场项目实战,别再担心面试没有实战项目:

[外链图片转存中…(img-3s7Ml85O-1714710988755)]

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值