MYSQL 的 MVCC 机制


MVCC 概念

Multi-Version Concurrency Control,即多版本并发控制

当前读
当前读即加锁读,读取记录的最新版本号,会加锁(排他锁)保证其他并发事物不能修改当前记录,直至释放锁。插入/更新/删除操作默认使用当前读,显示的为select语句加lock in share mode或for update的查询也采用当前读模式

快照读
不加锁,读取记录的快照版本,而非最新版本,使用MVCC机制,最大的好处是读取不需要加锁,读写不冲突,用于读操作多于写操作的应用(串行级别下的快照读会退化成当前读)

因此 MVCC 主要适用于Mysql的 RC , RR 隔离级别,而另外两个隔离级别:

  • MVCC的创建版本和删除版本只要在事务提交后才会产生,RU 能读到未提交事务的数据行,所以不适用MVCC.
  • 串行化是使用表加锁,并非行锁,所以也不适用MVCC

为什么使用 MVCC?
在MYSQL中,MyISAM使用的是表锁,InnoDB使用的是行锁。而InnoDB的事务分为四个隔离级别,其中默认的隔离级别REPEATABLE READ需要两个不同的事务相互之间不能影响,而且还能支持并发,这点悲观锁是达不到的,所以REPEATABLE READ采用的就是乐观锁,而乐观锁的实现采用的就是MVCC。正是因为有了MVCC,才造就了InnoDB强大的事务处理能力。


MVCC 的原理

InnoDB中使用的MVCC,是通过在每行记录后面保存两个隐藏的列来实现的,
这两个列,分别保存了这个行的创建时的系统版本号(事务ID),另一个保存的是行的删除时的系统版本号。
每开始一个新的事务,系统版本号就会自动递增,事务开始时刻的系统版本号会作为事务的ID

具体的过程
1.insert

		start transaction;
		insert into t_test values(1,'moke_1') ;
		insert into t_test values(2,'moke_2');
		commit;

表:

id value create version delete version
1 moke_1 1 undefined
2 moke_2 1 undefined

2.select
InnoDB会根据以下两个条件检查每行记录:
a.InnoDB只会查找版本早于当前事务版本的数据行(也就是,行的系统版本号小于或等于事务的系统版本号),这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的.
b.行的删除版本要么未定义,要么大于当前事务版本号,这可以确保事务读取到的行,在事务开始之前未被删除.
只有a,b同时满足的记录,才能返回作为查询结果.

3.update

		update t_test set value = "moke_update" where id = 2;

表:

id value create version delete version
1 moke_1 1 undefined
2 moke_2 1 2
2 moke_2 2 undfined

4.delete

		delete from t_test where id=1;

表:

id value create version delete version
1 moke_1 1 3
2 moke_2 1 2
2 moke_2 2 undfined

版本链和 ReadView

版本链
其实 MVCC 机制不只有上面流程中的两个字段,还有一个字段用来存放回滚指针,指针指向的是这条记录的上一个版本。随着我们不断进行操作,就会形成一条版本链。
此外还存在一个隐含的自增ID的字段,如果数据表没有主键,InnoDB会自动产生一个聚簇索引。

ReadView
版本链中存在一个问题:版本链中的哪个版本是当前事务可见的?
而 Read View 就是事务进行快照读操作的时候生产的读视图(Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID。

为了更好的说明,定义一下各个隐藏字段的名称

DB_TRX_ID DB_ROLL_PTR DB_ROW_ID trx_list up_limit_id low_limit_id
最近修改(修改/插入)事务ID 回滚指针 隐含的自增ID(隐藏主键) 系统当前活跃事务的ID trx_list列表中事务ID最小的ID 尚未分配的下一个事务ID

遍历链表记录中 的 DB_TRX_ID,与 ReadView 维护的 trx_list 进行比较,比较的过程如下:

  • 首先比较 DB_TRX_ID < up_limit_id, 如果小于,则当前事务能看到 DB_TRX_ID 所在的记录,如果大于等于进入下一个判断。
  • 接下来判断 DB_TRX_ID 大于等于 low_limit_id , 如果大于等于则代表 DB_TRX_ID 所在的记录在Read View生成后才出现的,那对当前事务肯定不可见,如果小于则进入下一个判断。
  • 判断 DB_TRX_ID 是否在活跃事务之中,如果在,说明创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。
发布了96 篇原创文章 · 获赞 57 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/MOKEXFDGH/article/details/99833199