MYSQL的InnoDB对MVCC的实现

type
status
date
slug
summary
tags
category
icon
password

什么是MVCC(Multi-Version Concurrency Control)?

MVCC是一种并发控制机制,用于在多个并发事务同时读写数据库时保持数据的一致性和隔离性。其原理是通过在每个数据行上维护多个版本的数据来实现的,事务对数据行的操作,只是在操作数据行其中的一个版本(快照),而不是实际的数据。
具体如下:
💡
查询(SELECT)
当一个事务执行读操作时,它会使用快照读取。快照读取是基于事务开始时数据库中的状态创建的,因此事务不会读取其他事务尚未提交的修改。
💡
写操作(INSERT、UPDATE、DELETE)
当一个事务执行写操作时,它会生成一个新的数据版本,并将修改后的数据写入数据库,并且原始版本的数据仍然存在,供其他事务使用快照读取,这保证了其他事务不受当前事务的写操作影响。
💡
事务提交和回滚
提交:修改的数据将成为最新的版本
回滚:修改的数据会被撤销
💡
版本的回收
为了防止数据库中的版本无限增长,MVCC会定期进行版本的回收,删除不再需要的版本,释放空间。
另外再简单补充两个概念:一致性非锁定读锁定读
一致性非锁定读:普通SELECT查询,在MVCC中是从快照中读取数据,也即是读取历史数据,被称为快照读。这样,各个事务之间互不影响,即使有事务对数据执行DELETEUPDATE操作,也不需要等待锁的释放才能读到数据。
锁定读:执行select ... lock in share modeselect ... for updateinsertupdatedelete 操作,会强制读取最新的数据,也被称为当前读,并且会对读取的数据加锁。除select ... lock in share mode 加S锁外,其他操作都是加X锁。
  • S锁 共享锁,当前事务对数据加S锁,其它事务也可以加S锁(加X锁会被阻塞)。
  • X锁 独占锁,当前事务对数据加X锁,它事务不能加任何锁。

InnoDB对MVCC的实现

MVCC的实现依赖于:隐藏字段、Read View、undo log。

隐藏字段

InnoDB存储引擎为每行数据添加了三个隐藏字段
  • DB_TRX_ID(6字节):表示最后一次插入或更新该行的事务id(删除也被视为更新,会在 记录头 Record header 中的 deleted_flag 字段将其标记为已删除)
  • DB_ROLL_PTR(7字节)回滚指针,指向该行数据的上一个版本的undo log (就像链表一样,最新的版本在链表头)
  • DB_ROW_ID(6字节):如果没有设置主键且该表没有唯一非空索引时,InnoDB会使用该id来生成聚簇索引

Read View

Read View主要是用来做可见性判断,里面保存了“当前对本事务不可见的其他活跃事务”(这里的活跃事务指未提交的业务)。
  • m_low_limit_id:目前出现过的最大的事务 ID+1,即下一个将被分配的事务 ID。大于等于这个 ID 的数据版本均不可见
  • m_up_limit_id:活跃事务列表 m_ids 中最小的事务 ID,如果 m_ids 为空,则 m_up_limit_idm_low_limit_id。小于这个 ID 的数据版本均可见
  • m_idsRead View 创建时其他未提交的活跃事务 ID 列表。创建 Read View时,将当前未提交事务 ID 记录下来,后续即使它们修改了记录行的值,对于当前事务也是不可见的。m_ids 不包括当前事务自己和已提交的事务(正在内存中)
  • m_creator_trx_id:创建该 Read View 的事务 ID

undo log

undo log主要有两个作用:
  • 当事务回滚时用于将数据恢复到修改前的样子
  • 另一个作用是MVCC当读取记录时,若该记录被其他事务占用或当前版本对该事务不可见,则可以通过undo log 读取之前的版本数据,以此实现非锁定读
不同事务或者相同事务的对同一记录行的修改,会使该记录行的undo log成为一条链表,链首就是最新的记录,链尾就是最早的旧记录。
notion image

数据可见性算法

这里就不具体从源码上说明了,如果想从源码分析,请参考
简单概述该算法:
对于事务A、B…,每次执行select时会生成Read View(RR级别下,只会在第一次SELECT时生成),Read View中的参数和目标数据DB_TRX_id决定了哪些数据版本对当前事务可见,大致分为下面三种情况:
  1. 在创建Read View前,操作(增、改、删)目标数据的最新事务已提交,则该事务id指向的数据版本对当前事务可见。
  1. 在创建Read View后,目标数据才被其他事务所修改,则对应DB_TRX_ID代表的数据版本对当前事务不可见,通过DB_ROLL_PTR找到上一个版本的数据,继续判断,直到满足1的情况。
  1. 不确定,即最新操作该数据行的事务(DB_TRX_ID)在当前事务创建Read View的时候可能处于“活动状态”或者“已提交状态”。如果是已提交状态(或者说,除了当前事务有对目标数据操作,没有其他活跃事务了),则可见;如果是活动状态(创建Read View前已修改但未提交,或者创建后,再修改),则不可见

RC和RR隔离级别下MVCC的差异

在事务隔离级别RCRR(InnoDB 存储引擎的默认事务隔离级别)下,都是通过快照读,但它们生成Read View的时机却不同。
RC下,事务在每次SELECT开始时都会生成一个新的Read View,而RR,只会在第一次SELECT开始时生成Read View。正是由于这个原因,RC下能避免脏读但无法避免不可重复读。

📎 参考文章

 
  • GitTalk

© aya 2024