隔离级别实现的原理--读写锁|| MVCC(一次性快照读)

事务并发存在的问题

  1. 脏读:一个事务在提交之前,在事务过程中修改的数据,被其他事务读取到了。

  2. 不可重复读:一个事务在提交之前,在事务过程中读取以前的数据发生数据发生了改变

  3. 幻读:一个事务按照相同的条件读取以前检索过的数据时,缺发现了其他事务插入的新数据

  4. 更新丢失:两个并行操作,后进行的操作覆盖了先进行操作的操作结果

四种隔离级别

  1. 读未提交(RU):允许一个事务可以看到其他事务未提交的修改

  2. 读已提交(RC):允许一个事务只能看到其他事务已经提交的修改,未提交的修改不可见

  3. 可重复读(RR):确保如果一个事务中执行两次相同的select语句,都能得到相同的结果,不管其他事务是否提交这些修改

  4. 串行化(序列化):将一个事务与其他事务完全隔离开,读写不允许并发,保证了安全性

隔离级别实现的原理:

实现隔离机制的方法主要有两种:

  • 读写锁

  • 一致性快照读,即MVCC

读为提交,采取的是读不加锁原理

  • 事务读不加锁,不阻塞其他事务的读写

  • 事务写阻塞其他事务写,但不阻塞其他事务读

串行化

  • 所有select语句会隐示加共享锁

  • 读加共享锁,写加排它锁,读写互斥。如果有未提交的事务正在修改某些行,所有select这些行的语句都会阻塞

MVCC的实现原理

介绍:

  1. MySQL中innoDb引擎支持MVCC

  2. 应对高并发事务,MVCC比单纯的加行锁更有效,开销更小

  3. MVCC在读已提交(Read Committed) 和可重复读(Repeatable Read)隔离级别下起作用。

  4. MVCC既可以基于乐观锁又可以基于悲观锁

  • MVCC(多版本并发控制),它是通过读取历史版本的数据,来降低事务冲突。从而提高并发性能的一种机制。他的实现依赖于 隐示字段,undo日志,快照读&当前读,Read View。

  1. 隐藏字段

    • 对于InnoDB存储引擎,每一行记录都有两个隐藏列DB_TRX_ID、DB_ROLL_PTR,如果表中没有主键和非NULL唯一键时,则还会有第三个隐藏的主键列DB_ROW_ID

      • DB_TRX_ID,记录每一行最近一次修改(修改/更新)它的事务ID,大小为6字节;

      • DB_ROLL_PTR,这个隐藏列就相当于一个指针,指向回滚段的undo日志,大小为7字节;

      • DB_ROW_ID,单调递增的行ID,大小为6字节;

  2. undo日志

    • 事务未提交的时候,修改数据的镜像(修改前的旧版本)存到undo日志里。以便事务回滚时,恢复旧版本数据,撤销未提交数据对数据库的影响。

    • undo日志是逻辑日志。可以认为,当删除一个记录时,undo log中就会记录一条对应的insert记录,当update一条数据时,它就记录一条相应相反的update记录

    • 存放undo日志的地方,就是回滚段

    例:对事务A的操作过程

    1. 对DB_ROW_ID(单调递增的行ID)= 1 的这行记录加锁

    2. 把该行的原有值拷贝到undo log中,DB_TRX_ID(记录最新修改的一个事务ID) 和DB_ROLL_PTR(指向回滚) 都不动

    3. 修改该行的值这是产生一个新的版本,更新DATA_TRX_ID 为修改记录的事务ID,将DATA_ROLL_PIR指向刚刚拷贝到undo log链中的旧版本记录,这样可以通过DB_ROLL_PTR(指向回滚的指针)找到这条记录的历史版本。如果对同一行记录执行连续的UPdate,undo log会组成一个链表,遍历这个链表可以看到这条记录的变迁。

    4. 记录redo log 包括undo log中的修改

    多个事务并向操作一行数据时,不同事务对该行数据的修改会产生多个版本,然后通过回滚指针连一条undo日志连。

    快照读:

    • 读取的是记录数据的可见版本(有旧的版本),不加锁,普通的select语句都是快照读

    当前读:

    • 读取的是记录数据的最新版本,显示加锁的都是当前读

      select * from account where id>2 lock in share mode
  3. (如何实现一致性读)--Read View

    解释:在读未提交(RU)隔离级别下,直接读取版本的最新记录,对于(序列化)隔离级别下,则是通过加锁互斥来访问数据,因此不需要MVCC的帮助。因此MVCC运行在RC(读已提交)和RR(重复读)这两个隔离级别下,当innodb隔离级别设置为二者其一时,在select数据就会用到版本链

    核心问题:版本链中那些版本对当前事务可见?

    innoDB 为了解决这个问题,设计了ReadView(可读视图)的概念

    RR下的ReadView生成

    在RR隔离级别下,每个事务touch first read时(本质上就是执行第一个select语句时,后续所有的select都是复用这个ReadView,其他update,delete,insert语句和一致性读的建立没有关系),会将当前系统中所有的活跃事务拷贝到一个列表生成ReadView。

    RC下的ReadView生成

    在RC隔离级别下,每个select语句开始时,都会重新将当前系统中所有的活跃事务拷贝到一个列表生成ReadView.

    与RR区别:就在于生成ReadView的时间点不同,一个是事务之后第一个select语句开始,一个是事务中每条select语句开始

    ReadView 中是当前活跃的事务 ID 列表,称之为 m_ids,其中最小值为 up_limit_id,最大值为 low_limit_id,事务 ID 是事务开启时 InnoDB 分配的,其大小决定了事务开启的先后顺序,因此我们可以通过 ID 的大小关系来决定版本记录的可见性,具体判断流程如下:

    1. 如果被访问版本的 trx_id 小于 m_ids 中的最小值 up_limit_id,说明生成该版本的事务在 ReadView 生成前就已经提交了,所以该版本可以被当前事务访问。

    2. 如果被访问版本的 trx_id 大于 m_ids 列表中的最大值 low_limit_id,说明生成该版本的事务在生成 ReadView 后才生成,所以该版本不可以被当前事务访问。需要根据 Undo Log 链找到前一个版本,然后根据该版本的 DB_TRX_ID 重新判断可见性。

    3. 如果被访问版本的 trx_id 属性值在 m_ids 列表中最大值和最小值之间(包含),那就需要判断一下 trx_id 的值是不是在 m_ids 列表中。如果在,说明创建 ReadView 时生成该版本所属事务还是活跃的,因此该版本不可以被访问,需要查找 Undo Log 链得到上一个版本,然后根据该版本的 DB_TRX_ID 再从头计算一次可见性;如果不在,说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被访问。

    4. 此时经过一系列判断我们已经得到了这条记录相对 ReadView 来说的可见结果。此时,如果这条记录的 delete_flagtrue,说明这条记录已被删除,不返回。否则说明此记录可以安全返回给客户端。

posted @ 2021-03-26 19:31  扣扣M  阅读(544)  评论(0)    收藏  举报