搞懂mysql中的mvcc机制
MVCC的介绍
mvcc称为多版本并发控制。这里有两个关键字,一个是多版本,一个是并发。
使用的主要目的
mvcc的出现是为了解决读写不冲突的问题,让我们MySQL在进行数据更改的时候,依然可以进行无锁去读。
基础知识
在搞清楚mvcc的时候,我们必须要搞清楚几个基础知识。
第一在MySQL的锁当中,无论你上的是独占锁还是共享锁,在修改数据的时候都是不可读不可写的。
读写问题如何解决
而在咱们MySQL的大多数场景当中,是以读为主,如果每一次修改都阻塞我们去读,那么性能就不会很好。
这个事情不可避免,那怎么样可以解决这种问题呢?
就是让读和写发生在不同的数据版本。不要绕让他们读同一份数据就可以了。于是就产生了undo log日志。undo log日志会在更新语句之前将原有的数据记录为历史版本。将最新的数据记录为当前版本。
多版本
所以此时会产生两种读操作,一种叫当前读,一种叫快照读。快照读其实读的是历史版本,而当前读读的是最新版本。
当前版本和历史版本通过一个隐藏字段叫roll point来进行互相的连接。这就是MVCC当中的多版本的含义。
并发控制
接下来是并发控制。
所以现在最大的问题就是当多个事务同时产生,需要访问相同数据的时候,他们应该如何选择与之匹配的版本。这就叫版本控制。
MySQL通常是通过事务ID来进行具体的版本选择,MySQL会为每一个事务分配一个具体的事务ID。这个事务ID由一个全局的递增变量来进行控制,每一次修改数据,都会将修改这个数据的事务ID,存入一个隐藏字段叫txid当中。
于是每一个版本的数据都会多一个字段叫事务ID。我们可以根据这个事务ID进行版本选择。
当然,仅仅有这些还不够,在有大量并发事务产生的时候,会产生很多的事务ID。这些有的事务是已经被提交的,有的事务是尚未提交正在运行的。在众多的事务当中,选择一个合适的版本并不是一件容易的事。
readview
readview叫读视图或者叫读快照。实际上一个readview就是c语言里边的一个结构体,里面包含一些数据。如果你不懂结构体,你就把它当作Java里边的一个类。然后里边的数据当成成员变量理解。这些成员变量包含以下内容:
- 当前事务的ID
- 预分配事务的ID(就是下一次分配事务的时候的那个ID)
- 正在运行但尚未提交的ID所组成的一个数组
根据读视图选择数据版本的方法
当开启一个事务并进行select操作的时候,就会生成一个读视图。根据读视图来选择数据版本的方式如下:
- 他会从当前数据开始,沿着版本链,逐个和读视图进行事务ID对比。如果当前最新的数据和当前咱们的事务ID一样,那直接返回最新数据的版本就可以了。说明没有其他的事务在操作逐个数据。
- 但如果发现当前数据的事务ID和我们当前事务的ID不一致。那就要判断当前数据的这个事务ID在不在这个未提交的事务ID列表当中。这个在咱们readview当中也是有保存的。如果不在,说明这个事务已经被提交。这个数据也是可以被读取的。如果在就说明当前正在运行的事务正在修改这个数据。那我们就不要去读这个数据。要沿着版本链,继续找下一条可用的数据。通过对历史版本的回溯对比,我们总能找到适合当前事务的数据进行读取。这就是数据版本的选择过程。
读已提交中不可重复读问题
大家都知道对于RC,也就是我们的读已提交的事务隔离级别,是存在不可重复读的问题。
MySQL又是怎么解决的呢?
在RC级别,每次select语句都会产生一个新的读视图,所以每次读取可能会产生不一样的数据。
可重复读解决不可重复读的原理
而在RR级别,也就是咱们的可重复读的隔离级别下,通常第一次select语句会产生一个读视图,在当前事务中,以后每次其他的select语句都复用第一次产生的readview。
这样就可以保证在一个事务当中,每一次进行select都可以得到相同的数据版本。这样就解决了咱们的不可重复读问题。
串行化
串行化相当于禁用了历史版本。每一次读取或修改必须使用当前版本。这样的话,性能是很差的。