Mysql 专栏 - MVCC机制
前言
mvcc机制是mysql解决事务问题一项重要机制,通过这个机制,mysql解决了对于事务的问题:脏写、脏读、反复读的问题,然而默认的不可反复读的状况下还是会呈现幻读的问题。
概述:
- undo log的版本链条和read view的实现
- Undo log以及read view如何解决常见的事务问题
- 简略介绍对于独占锁和共享锁的内容
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版本链条如下所示:
- 首先,事务A写入了一条数据并且写入的值为A,此时事务A id为50,所以在undo log的链条外面存储的也是trx_id
- 事务B往这一行记录更新一个值B,此时会更新trx_id 为58,并且roll_point 会指向trx_id 为 50的记录
- 如果此时呈现事务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想要获取表锁时,必须要确认表的每一行都不存在排他锁,很显著效率会很低,引入意向锁之后,效率就会大为改善:
- 如果事务A获取了某一行的排它锁,理论此表存在两种锁,表中某一行的排他锁和表上的动向排他锁。
- 如果事务B试图在该表级别上加锁时,则受到上一个意向锁的阻塞,它在锁定该表前不用查看各个页或行锁,而只需检查表上的意向锁。
上面是整个加锁的总结:
总结
MySQL实现MVCC机制的时候,是基于undo log多版本链条+ReadView机制来做的,默认的Read uncommit隔离级别,就是基于这套机制来实现的,依靠这套机制实现了RR(repeatable read)级别,防止脏写、脏读、不可反复读这三个问题,然而无奈防止幻读的问题。
在后续的内容中咱们介绍了对于事务的隔离级别问题,以及脏写的问题如何防止,最初咱们讲述对于加锁的规定和内容,以及简略理解意向锁是什么货色。
写在最初
以上就是对于mvcc的简略理解内容,只有粗浅理解undo log和read view这两个组件联合的机制置信mvcc的机制也能很快的了解。