前言
从第一次认识mvcc到今天终于搞懂mvcc大概过了两个月
之前一直觉得很复杂,但搞明白之后开始想不通自己之前为什么搞不懂
回顾
讲到mvcc,那就要先回顾一下数据库事务的四大特性
- 原子性(
Atomicity
):事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用; - 一致性(
Consistency
):执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的; - 隔离性(
Isolation
):并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的; - 持久性(
Durability
):一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
接着自然而然发问:如何保证这四大特性?
原子性:undo log保证
持久性:redo log保证
隔离性:mvcc
一致性:满足了原子性、持久性、隔离性,就能够保证一致性
好,那接下来就来看mvcc
什么是MVCC
MVCC(Multi Version Concurrency Control),中文名是多版本并发控制,简单来说就是通过维护数据历史版本,从而解决并发访问情况下的读一致性问题。
理解mvcc之前,先要理解几个概念
版本链
对于 InnoDB 存储引擎,每一行记录都有两个隐藏列
-
DB_TRX_ID:全称为 Database Transaction ID。这是一个在 InnoDB 存储引擎中用于标识事务的唯一标识符。每个事务都有一个唯一的
DB_TRX_ID
,用于区分不同的事务。这个标识符可以用于识别事务的开始和结束,并跟踪事务的状态。 -
DB_ROLL_PTR:全称为 Database Rollback Pointer。这是一个指向 InnoDB 存储引擎中事务回滚段(rollback segment)的指针。在事务执行期间,将对数据库进行修改的操作记录到事务日志中。
DB_ROLL_PTR
指向了这些修改的回滚段,以便在需要时进行回滚操作,确保数据库的一致性和持久性。
简单来说,DB_TRX_ID相当于事务id,它是按事务发起顺序自增的;DB_ROLL_PTR相当于指针,指向的是这行记录的前一个状态。由此我们知道,mvcc中的每一条记录都相当于一个链表
ReadView
在innoDB中,Read View 负责管理事务在执行期间看到的数据库快照(snapshot),以确保事务在执行过程中看到一致性的数据视图。
简单来说,每一条事务发起的时候,会生成一个ReadView,这条ReadView中包括以下四个信息
- m_ids :表示在生成 ReadView 时当前系统中活跃的读写事务的事务 id 列表。
- min_trx_id :表示在生成 ReadView 时当前系统中活跃的读写事务中最小的 事务 id ,也就是 m_ids 中的最小值。
- max_trx_id :表示生成 ReadView 时系统中应该分配给下一个事务的 id 值。
- creator_trx_id :表示生成该 ReadView 的事务的 事务 id
那么,有了这些记录,就可以知道当前数据库中的某些数据是不是自己应该读到的了,如果当前数据是自己不该看到的,那就沿着链表往上找,知道找到自己应该看到的
规则如下:
- 如果被访问版本的 DB_TRX_ID 属性值与 ReadView 中的 creator_trx_id 值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
- 如果被访问版本的 DB_TRX_ID 属性值小于 ReadView 中的 min_trx_id 值,表明生成该版本的事务在当前事务生成 ReadView 前已经提交,所以该版本可以被当前事务访问。
- 如果被访问版本的 DB_TRX_ID 属性值大于 ReadView 中的 max_trx_id 值,表明生成该版本的事务在当前事务生成 ReadView 后才开启,所以该版本不可以被当前事务访问。
- 如果被访问版本的 DB_TRX_ID 属性值在 ReadView 的 min_trx_id 和 max_trx_id 之间,那就需要判断一下 trx_id 属性值是不是在 m_ids 列表中,如果在,说明创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被访问。
注意
事务在开始时分配一个唯一的事务 ID,并且在执行期间持有这个事务 ID。当事务提交时,它的事务 ID 就会在数据库中标记为已提交状态,以表示该事务已经完成。而如果事务回滚,那么相应的事务 ID 就会被标记为回滚状态。
那么看到这里,整个mvcc大概就是这么一个机制,其实也不难
四个隔离机制如何实现
读未提交:不加任何锁,可以读取mvcc中未提交的事务
读已提交:加读锁(记录锁),只能读取mvcc中已提交的事务
可重复读:前两者都是在每一次操作的时候获得当前的readview,而可重复读仅在事务开始的时候获取readview,整个事务共用这个readview
串行化:加读写锁(间隙锁和临键锁)