关于mysql:Mysql-专栏-MVCC机制

41次阅读

共计 4546 个字符,预计需要花费 12 分钟才能阅读完成。

Mysql 专栏 – MVCC 机制

前言

​ mvcc 机制是 mysql 解决事务问题一项重要机制,通过这个机制,mysql 解决了对于事务的问题:脏写、脏读、反复读的问题,然而默认的不可反复读的状况下还是会呈现幻读的问题。

概述:

  1. undo log 的版本链条和 read view 的实现
  2. Undo log 以及 read view 如何解决常见的事务问题
  3. 简略介绍对于独占锁和共享锁的内容

mysql 事务问题:

​ 事务的问题无非上面两种:

  • 多个事务并发执行的时候,可能会同时对缓存页里的一行数据进行更新,这个抵触怎么解决? 是否要加锁?
  • 可能有的事务在对一行数据做更新,有的事务在查问这行数据,这里的抵触怎么解决?

undo log 事务回滚的实现

根本介绍:

​ 首先咱们须要理解 undo log 中存在两个重要的属性,一个是trx_id,一个是roll_pointer,这个 trx_id 就 是最近一次更新这条数据的事务 id,roll_pointer 就是指向你了你更新这个事务之前生成的 undo log,对于 undo log 之前都讲过了这里不再过多的介绍。

Undo log 版本链

​ 介绍 mvcc 机制之前,咱们须要先理解对于 undo log 的版本链条的构造,很显著这个机制的引入是为了 mvcc 的机制铺路筹备的,上面咱们来一一解说 undo log 回滚机制

是什么?

​ undo log 版本链条是通过链表的形式,当同一条记录存在多个事务提交的时候,为了保障事务能够失常回滚,会通过 roll_pointer 以及 txd_id 实现多个事务版本的串联,同时串联多个版本的值。

​ 比方上面这个 undo log 版本链条如下所示:

  1. 首先,事务 A 写入了一条数据并且写入的值为 A,此时事务 A id 为 50,所以在 undo log 的链条外面存储的也是 trx_id
  2. 事务 B 往这一行记录更新一个值 B,此时会更新 trx_id 为 58,并且 roll_point 会指向 trx_id 为 50 的记录
  3. 如果此时呈现事务 C 更新数据,依照同样的情理也会呈现一个链表的节点并且将事务 C 指向 trx_id 为 69 的记录

​ 至此,一个 undo log 多版本控制链曾经实现,他的构造如上面的模式:

这种设计有什么作用?

​ 这种设计的作用是,保障多个事务提交的时候一旦须要回滚操作,能够保障同一个事务只能读到比以后版本更早提交的值,不能看到更晚提交的值。

Read view

是什么?

​ read view 是 mvcc 机制的实现一个要害组件,是 mysql 基于 undo log 多版本链条实现的,在一个事务开启的时候,默认会为以后事务生生成一个 read view 表,这个表在事务开启的时候所有的参数都确定,并且在事务完结的时候才会销毁。

​ 一个 read view 当中外面蕴含了上面的内容,蕴含 4 个重要的根本字段,其中最重要的是 m_ids 以及 max_trx_id 以及 min_trx_id 这三个字段。

  • 一个是 m_ids,这个就是说此时有哪些事务在 MySQL 里执行还没提交的;
  • 一个是 min_trx_id,就是 m_ids 里最小的值;
  • 一个是 max_trx_id,这是说 mysql下一个要生成的事务 id,就是最大事务 id;
  • 一个是 creator_trx_id,就是你这个事务的 id

留神 max_trx_id 是下一个要生成的事务 id,之所以这样设计是不便事务 id 的判断

​ 依照逻辑的简略了解,他的存储构造如下所示,具体的参数的含意会在下文依据理论的案例进行解释,这里只是作为展现数据的存储展现了解即可。

检查和读取步骤

​ 理解了下面的根本结构图之后,上面咱们来理解一下如何读取和检索来实现一个 undo log 的回滚操作,为了更好的了解咱们须要依据下面的图退出一些模仿的操作来进行解释:

假如事务 A 须要读取数据,而事务 B 须要更新数据,事务 A 的 id 为 50,事务 B 为 58,依照 read view 的设计,最终会呈现一副上面的状况:

如果不加以任何限度,这里会呈现脏读的状况,也就是一个事务可能会读到一个没有提交的值。

​ 然而实际上必定不是这样的,依照下面的 undo log 链的介绍,事务 A 须要查问值然而在查问的过程中忽然被事务 B 插了一脚把这个值更新了,此时须要生成一个 undo log 的记录,并且让其值更新为事务 B 提交的值,所以实际上结构图会是上面的样子,这里读者能够先暂停一下思考事物 A 要怎么读,又是怎么去判断的:

要害:此时事务 A 去读取的时候,本人的 id 是 50,然而发现 trx_id 却是 58,也就是事务的 id 要比本人操作的记录 id 要新,然而它是如何晓得的?其实就是用了这个 read view,此时事务 A 查问 min_trx_id 发现他的 min_trx_id(50)是小于以后记录的 trx_id(58)的,阐明此时很有可能在事务 A 开启的时候呈现了其余差不多工夫开启的事务在操作这个数据,于是会看一下 m_ids 列表,发现果然有其余事务在进行烦扰

​ 于是事务 A 就晓得这条数据不是他改的,所以它要依据 roll_point 找到下一条数据(此时能够了解这条数据为事务 A 操作的快照)并且同样查看他的 trx_id 是否大于 min_trx_id,通过比照发现是和他相等的(都是 50),所以能够确定这条数据是事务 A 批改的数据。最终他的判断结构图如下所示:

​ 通过下面的步骤解说,咱们简略理解 undo log 版本链和 read view 是如何配合实现 mvcc 这个机制的:在每一个事务开启的时候,会保护一个 read view 链表,当多个事务同时操作一个记录行的时候,就会依据 undo log 版本链构建一个链表构造的记录链条串,通过这两个构造的配合,实现了根本的 mvcc 的操作。

解决脏读、幻读、反复读问题

​ 有了下面的 Read View 和 undo log 链条之后,上面咱们来看下如何解决脏读,幻读以及反复读的问题的。其实情理都是互通的这里简略阐明一下要害的局部:

脏读:

​ 脏读就是读到一个未提交的数据,依据下面的内容介绍如果事务 A 读到一个没有提交的值,会依据 undo log 链找到事务 A 提交的值,而后只有事务 A 读取的是本人开启事务的时候看到的值,就没有问题。

不可反复读:

​ 反复读和脏读相似,不过会略微的绕一下,假如事务 A 须要读取一个有可能被事务 B 批改的值,会有两种状况:

1. 读取事务 B 批改之后的值,2. 读取事务 B 批改了然而没有提交事务的值。

​ 对于第二点咱们能够通过 undo log 的形式回溯找到事务 A 之前读取的值并且进行操作即可,这样事务 A 操作后果就不是脏读的。事务 B 其实更加好了解,因为事务 B 曾经把事务提交了,当事务 A 再次查问会生成一份新的 Read View,此时事务 B 曾经不再 m_ids 外面,也就是说,尽管事务 A 发现 undo log 中的 trx_id 并不是本人批改的值,然而又发现 m_ids 事务没有这个未知的“篡改者”,所以能够认定这个事务曾经被批改了,这时候操作是没有影响的。

幻读:

​ mysql 避免幻读的操作其实就是在事务 B 操作提交实现事务之后,事务 A 发现这个 ID 是大于以后的 max_trx_id 的是不可能进行读取的,也就是说它只能读到小于这个 max_trx_id 的数据,mysql 就是通过这种形式来防止幻读问题的产生的。

多个事务如何防止脏写?

​ 理解了 mvcc 机制之后,咱们来理解多事务执行的时候如何防止脏写这个问题,也就是如何防止一个事务读到一个未提交的内容:

​ 这里咱们间接依据一个图进行解说,当第一个事务拜访的时候,此时这个事务就会创立一个锁,外面蕴含了本人的 trx_id 和期待状态,而后把锁跟这行数据关联在一 起,同时锁是在内存外面实现操作的,因为操作数据在缓冲区实现的而不是磁盘文件实现。第二个事务过去的时候,发现数据被锁了,所以很无奈,只能进行期待,此时会生成一个锁,并且把期待状态设置为 true,示意本人也在期待。要害的一步来了:事务 A 执行实现之后会解除锁并且会去找找有没有事务也对这条数据加了锁,后果发现第二条事务也加了锁,于是会去开释这个锁,而后把 B 唤醒去干活。这种双锁的设计保障了多线程拜访的状况下对于一行数据的拜访。

事务隔离级别解决的事务问题

​ 在讲述下一个内容之前,咱们先回顾一下事务隔离级别是如何解决事务问题的。

隔离级别 脏读 不可反复读 幻读
读未提交(read uncommitted RU)
读提交(read committed RC) ×
可反复读(repeatable read RR) × ×
串行化(serializable) × × ×

独占锁和共享锁

​ 讲完了事务咱们接下来来简略聊聊对于独占锁和共享锁的特点。

多个事务运行 的时候 mysql 退出的是 独占锁 ,然而因为应用的了 Mvcc 的机制,所以又分为了 读锁和写锁,写锁的优先级是高于读锁的,然而 mysqL 通过 mvcc 实现了读写锁的拆散操作,也就是一条数据更新的时候,并不影响另一个事务读取数据,这样也保障了互斥锁造成事务阻塞的问题。

共享锁和独占锁是互斥的,你只能加其中的一个锁。最初咱们来看看共享锁和独占锁的互斥问题

执行查问操作就是想要加锁怎么办?

​ MySQL 首先反对一种共享锁,就是 S 锁,这个共享锁的语法如下:select * from table lock in share mode,你在一个查问语句前面加上lock in share mode,意思就是查问的时候对一行数据加共享锁。

​ 下面提到的锁都是行锁的个性,在多个事务并发更新数据的时候,都是要在行级别加独占锁的,这就是行锁,独占锁都是互斥的,所以不可能产生脏写问题,一个事务提交了才会开释本人的独占锁,唤醒下一个事务执行

​ 所以如果更新数据,会呈现上面的状况:

  • 第一种是基于 mvcc 的事务隔离机制
  • 第二种是基于非凡语法的独占锁和共享锁

须要留神的是 dll 语句和增删改的操作是互斥的

行锁和表锁的加锁规定

如何加表锁

​ 加表锁通常应用上面两条语句,然而实际上这个加锁的操作

LOCK TABLES xxx [READ: 这是加表级共享锁](READ: 这是加表级共享锁)

LOCK TABLES xxx WRITE: 这是加表级独占锁

意向锁

​ 对于意向锁的内容,咱们须要理解的是在增删改的时候会进行动向的独占锁,而查问的时候会退出动向的共享锁,什么是意向锁呢?假如,事务 A 获取了某一行的排它锁,尚未提交,此时事务 B 想要获取表锁时,必须要确认表的每一行都不存在排他锁,很显著效率会很低,引入意向锁之后,效率就会大为改善:

  1. 如果事务 A 获取了某一行的排它锁,理论此表存在两种锁,表中某一行的排他锁和表上的动向排他锁。
  2. 如果事务 B 试图在该表级别上加锁时,则受到上一个意向锁的阻塞,它在锁定该表前不用查看各个页或行锁,而只需检查表上的意向锁。

上面是整个加锁的总结:

总结

​ MySQL 实现 MVCC 机制的时候,是基于 undo log 多版本链条 +ReadView 机制来做的,默认的 Read uncommit 隔离级别,就是基于这套机制来实现的,依靠这套机制实现了 RR(repeatable read)级别,防止脏写、脏读、不可反复读这三个问题,然而无奈防止幻读的问题。

​ 在后续的内容中咱们介绍了对于事务的隔离级别问题,以及脏写的问题如何防止,最初咱们讲述对于加锁的规定和内容,以及简略理解意向锁是什么货色。

写在最初

​ 以上就是对于 mvcc 的简略理解内容,只有粗浅理解 undo log 和 read view 这两个组件联合的机制置信 mvcc 的机制也能很快的了解。

正文完
 0