什么是MVCC
MVCC即为多版本并发控制,是一种用于提高并发量的方法,其可以有效的提高innodb引擎数据库的并发性能,做到即使有读写冲突,也能不加锁并发读。
什么是当前读和快照读
当前读:select lock in share mode(共享锁), select for update ; update, insert ,delete(排他锁)这些操作都是一种当前读,即读取当前的最新数据,为了保证数据是最新的,需要对读的数据加锁,不让其他事务在读的期间修改
快照读:不加锁的select就是快照读,从字面理解,就是将数据取一份当前的快照,然后select这个快照上的数据,也不会加锁。当select数据时,此数据可能已经不是最新的了(被其他事务修改),显然此时的隔离级别不能是串行化,否则不会有其他事务并行修改。
为什么需要快照读
为了提高并发度,降低锁等待的时间
如何实现快照度
利用MVCC,MVCC避免了加锁操作,降低了开销,实现了读写冲突时不加锁(仅限快照读)
MVCC涉及的部分
一,MVCC的实现利用了每行记录的三个隐藏字段,分别时DB_TRX_ID(最近修改本行数据的事务id),DB_ROLL_PTR(undo log带来的回滚指针,指向本行数据的上个版本),DB_ROW_ID(本行数据的隐藏主键,如果没有主键,会以此主键生成表)
ps,还有第四个隐藏字段 flag_delete,删除标记,实际删除由purge线程完成,与本文无关
二,MVCC的实现还利用了Read VIew,Read View就是在进行快照读时生成一个事务快照,他会记录当前数据库内begin但未commit的事务id。这个View包含三部分,1,维护当前begin未commit的事务id的list ,记为trx_list ;2,list事务里的最小事务id,记为trx_min_id;3,View生成时刻,最大事务id+1的值,记为trx_max_id_plus ,也可以认为是下一个将要分配的事务id,需要注意的是这个最大事务是全局的,不是list里的事务,这与第二点不太相同。
Read View遵循可见性算法,具体规则如下
1,事务id小于trx_min_id,那么此事务可见,
2,事务id大于trx_max_id_plus,此事务不可见
3,事务id是否在trx_list中,在则不可见,不在则可见。
三条规则依次判断,这里的可见和不可见指的是能否看到此事务修改的值
三,MVCC的实现还利用了undo log,确切的说的update undo log。当update某行数据时,先加行锁,修改本行数据,然后将相应的undo log保存,同时回滚指针将指向undo log的位置,最后释放锁。如果希望获得update之前的数据,只需要将回滚指针指向的undo log应用到本行数据上即可。如果有多个版本的数据,那么会形成链表结构,链表最后端为最新数据,最前端为最老数据,不同版本之间也是通过回滚指针相连接。如果要获得最老的数据,就拿最新数据按照链表的顺序,一个一个的应用undo log即可。
MVCC的具体实现
结合图说
-
图中表示的是,一行数据的三个不同版本,其中字段1为a的是第一个版本,b为第二个版本,c为第三个版本,即当前版本
-
因为是同一行数据,所以主键和row_id在不同版本里是一样的,
-
roll_ptr为回滚指针,它指向的是上一个版本的数据位置
-
trx_id表明了是哪一个事务修改的本行数据。
此时 假设事务3(trx_id=3) select 本行数据,假设此时的read view 为{2,3,4},那么首先获取最新版本数据的trx_id:9,由于9> 4+1,所以最新版本的数据c对事务3不可见;然后依据row_ptr指针找到本行数据的上一个版本,同理 由于trx_id 4 in read view,所以此版本依然不可见;最后依据回滚指针找到上上版本,此时trx_id:1< 2,所以此版本可见,那么此时select出的数据就是 a。
RR和RC下 READ VIEW有何不同
从以上的流程可以看出 对于一个事务能读出那条数据,关键在于read view的内容。而read view的内容取决于read view生成的时间。
在RC下,事务在每一次进行快照读时,会生成一个read view, 如果在两次快照读之间有事务修改数据并commit,那么前后的两次快照读select这行数据的结果就会不同,这就是RC会出现不可重复读的原因,同一事务的两次读出现不一样的解决。
在RR下,当事务第一次进行快照读时,会生成一个read view,此后本事务内的所有快照读均使用该read view,不会再生成其他read view,所以在整个事务内,读同一行的数据不会有变化,这就解决了不可重复读问题。不可重复读对应的update操作,而如果是insert呢,RR也存在幻读的问题。对于这个问题可以用 串行化 解决,但是串行化效率太低,所以可以用间隙锁加插入意向锁解决。