面试必问之事务的隔离级别与MVCC

2023-02-28 14:02:24 浏览数 (2)

大家好,我是热心的大肚皮,皮哥。

事务的隔离级别

可串型化执行

对一个服务器来说,可以有多个客户端连接,而且每一个事务都对应一次数据状态的变换,为了保证事务的ACID4大特性,如果单纯的串行的执行事务,则会降低系统吞吐量与资源利用率,所以mysql选择了可串型化执行,舍弃了一部分隔离性来换取一部分性能,也就是某个事务操作某个数据时,其他试图操作相同的数据的事务需要等待,当事务提交时,其他操作相同的数据的事务才可能进行操作,不操作相同的数据则并行操作。

事务并发执行遇到的一致性问题

  • 脏写:指一个事务修改了另一个未提交事务的数据。
  • 脏读:指一个事务读取了另一个未提交事务修改的数据。
  • 不可重复读:指一个事务修改了另一个未提交事务读取的数据。
  • 幻读:一个事务根据条件搜索出部分数据,但在未提交时,另一个事务写入了一些符合条件的数据。

严重性是,脏写>脏读>不可重复读>幻读。

SQL标准中的4种隔离级别

  • READ UNCOMMITTED:未提交读。可能发生脏读、不可重复读、幻读。
  • READ COMMITTED:已提交读。可能发生不可重复读、幻读,不会发生脏读。
  • REPATABLE READ:可重复读。仅可能发生幻读。
  • SERIALIZABLE:可串行化。均不会发生。

MVCC-多版本并发控制

版本链

每次记录的改动都会生成一个undo日志,每个undo日志都有一个roll_pointer,通过这些roll_pointer组成一个链表,也就是版本链。

代码语言:javascript复制
insert into hero values(1,'刘备', '蜀');
update hero set name='关羽' where number =1;
update hero set name='张飞' where number =1;
update hero set name='赵云' where number =1;
update hero set name='马超' where number =1;

对应的版本链如下。

ReadView

ReadView一致性视图主要作用是判断版本链中哪个版本的事务是当前事务可见的,主要包含4个比较重要的内容。

  • m_ids:生成ReadView时,当前系统中活跃的读写事务id列表。
  • min_trx_id:在生成ReadView时,当前系统中活跃读写事务中最小的事务id,也是m_ids 的最小值。
  • max_trx_id:生成ReadView时,系统应该分配下一个事务的事务id。
  • creator_trx_id:生成ReadView的事务id。

ReadView的使用

具体使用方式如下。

  • 如果被访问版本的trx_id与ReadView中的creator_trx_id相同,则代表当前事务在访问自己修改过的记录,可以访问。
  • 如果被访问版本的trx_id小于ReadView中的min_trx_id,则代表生成该版本的事务在生成ReadView之前提交,可以访问。
  • 如果被访问版本的trx_id大于ReadView中的max_trx_id,则代表生成该版本的事务在生成ReadView之后,不可以访问。
  • 如果被访问版本的trx_id在ReadView中的min_trx_id与max_trx_id之间,则需要判断是否在m_ids中,如果在,代表trx_id这个事务属于活跃中,则不可访问;否则代表已经提交,可以访问。

那么上面说的4种隔离级别何时生成的呢?

  • READ UNCOMMITTED(未提交读):直接访问最新版本即可。
  • READ COMMITTED(已提交读):每次读取数据前都生成一个ReadView。比如,现在系统中有两个事务正在执行,分别是100、200。
代码语言:javascript复制
# Transaction 100 
BEGIN;
update hero set name='关羽' where number =1;
update hero set name='张飞' where number =1; 
# Transaction 200
BEGIN;
#修改了一些别的表记录

表中number为1的记录对应的版本链如下。

假设现在有个新事务开始执行。

代码语言:javascript复制
BEGIN;
#select1: transaction 100、200未提交
select * from hero where number=1;#得到的列name为'刘备'

具体过程如下。

步骤1 在执行select语句时先生成一个ReadView。这时候ReadView中的m_ids的列表内容就是[100,200],min_trx_id为100,max_trx_id为201,creator_trx_id为0。

步骤2 然后从版本链中挑可见的数据。最新的name为张飞,但是这个版本的trx_id为100,在m_ids列表内,不符合要求。根据roll_pointer跳到下一个版本。

步骤3 下一个关羽原因同上,继续跳到下一个版本。

步骤4 下一个为刘备,trx_id为80,小于ReadView中的min_trx_id值100。符合要求,返回数据。

  • REPATABLE READ(可重复读):第一次读取时生成ReadView。过程如上。
  • SERIALIZABLE:这个不过多介绍。

二级索引与MVCC

具体使用方式如下。只有聚簇索引才有trx_id和roll_pointer,那么使用二级索引执行查询如何判断呢?分为两个步骤。

步骤1 二级索引页面的Page Header有个PAGE_MAX_TRX_ID的属性,每次对该页面进行增删改操作时,如果操作的事务id大于PAGE_MAX_TRX_ID,则将PAGE_MAX_TRX_ID设置为当前事务id,在查询时,根据min_trx_id与PAGE_MAX_TRX_ID进行比较,如果大于则可见,否则执行步骤2。

步骤2 利用二级索引的主键进行回表,接下来的过程就是聚簇索引的多版本并发控制过程了。

0 人点赞