mysql的MVCC原理
MVCC:Multiversion concurrency control,多版本并发控制,提供并发访问数据库时,对事务内读取的到的内存做处理,用来避免写操作堵塞读操作的并发问题.
InnoDB中锁可以分成读锁跟写锁,读锁与写锁是互斥的,通过锁机制可以实现解决事务并发带来的脏读,不可重复读,幻读,但是加锁的话开销大,数据库本身除了安全性还需要有较好的性能,所以就用MVCC代替了读锁,减少了加锁解锁的开销和线程等待,也就是通过select 查询直接走MVCC,使得能够与客户update等写操作并发执行,并且解决事务并发的脏读,不可重复度,幻读等问题.
MVCC在mysql中只应用在read committed 和 repeatable read 两个事务隔离级别中,serializable使用的读锁+写锁直接锁表,强制事务串行化,应用非常少,并发性很低.
mysql中不同的锁的概念比较多,MVCC中锁概念分快照读与当前读,快照读是指select不加锁通过MVCC来控制,当前读是指insert delete update,for update,lock inshare mode需要加排他锁
mysql MVCC的实现是靠以下几个方面合作实现的:
1
InnoDB中每一条记录有隐藏列(以下两个列主要用于update跟insert,还会有一个删除标记,用于delete,被标记的数据是删除的数据)
DB_TRX_ID 长度为6个字节,存入插入或者更新语句中的最后一个事务id
DB_ROLL_PTR 长度为7个字节,称之为:回滚指针,指向写入回滚段的undo log记录.
DB_ROW_ID 如果有主键的情况下不会生成这列数据
每次事务开启系统都会分配一个事务id,并且事务id是递增的.
如果一个事务开启后事务id为1,那么下一个事务开启事务id就是2
2 undo log
update以及delete时,并不是将数据直接更新或者直接删除,而是copy出一条要操作的记录,将另外一条数据更新,并且更新DB_TRX_ID为操作的事务ID,更新DB_ROLL_PTR为数据之前的DB_TRX_ID:
比如id=1 name=‘li’ 这一条记录,是事务id为1的事务插入的
这条数据是这样的
id | name | DB_TRX_ID | DB_ROLL_PTR |
---|---|---|---|
1 | li | 1 | null |
事务id为2的事务进行了更新,把name改成了’zhao’
此时先把上面的数据copy到undo log中,然后把数据更新成下面这样
id | name | DB_TRX_ID | DB_ROLL_PTR |
---|---|---|---|
1 | zhao | 2 | 1 |
第二条的DB_ROLL_PTR 为1 指向第一条的DB_TRX_ID,也就是形成了一个链式结构,可以根据DB_ROLL_PTR 找到记录更新的链路.
delete也是这样,只是会在记录中标记删除标记
undo log用以实现事务中的roll back,并且可以控制事务只能看到某些版本中的数据.
3 read view
read view保存有这些信息:
m_ids:表示在生成ReadView时当前系统中活跃的读写事务的事务id列表。
min_trx_id:表示在生成ReadView时当前系统中活跃的读写事务中最小的事务id,也就是m_ids中的最小
值。
max_trx_id:表示生成ReadView时系统中应该分配给下一个事务的id值。
creator_trx_id:表示生成该ReadView的事务的事务id。
这些信息用来确保数据是否可见:
如果DB_TRX_ID 等于creator_trx_id:说明数据是当前事务的,所以可见
如果数据DB_TRX_ID 小于min_trx_id,数据数据已经提交,所以这行数据可见
如果数据DB_TRX_ID 大于等于max_trx_id说明开启当前事务之前,数据没有提交,所以不可见
如果数据DB_TRX_ID大于等于min_trx_id小于等于max_trx_id中,如果在m_ids之中,说明这条数据的事务并没有提交,所以不可见,这样就解决了脏读问题,如果,不在m_ids中,说明已经提交,所以数据可见.
有了以上概念,分别说下RC级别跟RR级别是如何运作的.
RC只解决了脏读问题 RC事务隔离级别下,同一个事务中,每一次select都会生成一个read view
现有数据id=1,name=‘li’,事务A第一次select时生成readview,如果事务B在事务Aselect时已经开启,那么事务B的事务id将会在事务A的m_ids中,所以数据不可见,所以RC事务隔离级别下,是解决了脏读问题,但是由于事务A每一次select都会生成readview,如果事务B在事务A第一select之后就提交事务,事务A在第二次select时生成的readview中,事务B不在m_ids中(因为事务B已提交),并且此时max_trx_id要大于事务B的事务id,所以,事务B提交的数据对于事务A是可见的,所以不可重复读是没有避免的.
RR事务隔离级别下,事务A只有第一次select时才生成read view,并且后面select也是公用第一次生成的read view,所以在事务A后开启的事务肯定是大于等于事务A中的max_trx_id,与事务A同时开启的事务,会始终在m_ids列表中,所以更新的数据不可见,也就是说,RR事务隔离级别下,只有当前事务更改的操作,或者在当前事务select之前就提交的数据才能被本次事务可见,所以RR事务隔离级别是解决了不可重复读与幻读问题.
本文地址:https://blog.csdn.net/qq_28603127/article/details/107105839