mysql MVCC实现原理
作者:向前的步伐 / 发表: 2022年5月1日 08:42 / 更新: 2022年5月12日 08:27 / mysql / 阅读量:759
一、简介
MVCC (Multiversion Concurrency Control),即多版本并发控制技术,它使得大部分支持行锁的事务引擎,不再单纯的使用行锁来进行数据库的并发控制,取而代之的是把数据库的行锁与行的多个版本结合起来,只需要很小的开销,就可以实现非锁定读,从而大大提高数据库系统的并发性能。
二、事务隔离级别
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
未提交读(read-uncommitted) | 是 | 是 | 是 |
已提交读(read-committed) | 否 | 是 | 是 |
可重复读(repeatable-read) | 否 | 否 | 是 |
串行化(serializable) | 否 | 否 | 否 |
- 脏读:脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而且这种修改还没有提交到数据库中,这时,另外一个事务也访问了这个数据,然后使用了这个数据。
- 不可重复读:是指在一个事务内,多次读同一个数据。在这个事务还没结束的时候,另外一个事务也访问了该数据。那么,在第一个事务中的两次读数据之间,由于第一个事务的修改,那么第一个事务两次读取的数据可能是不一样的。这样就发生了一个事务两次读取同一数据是不一样的,因此称为不可重复读。
- 幻读:是指事务不是独立执行的时候发生的一种现象。例如一个事务对一个表进行了修改,涉及到了全表的数据行。同时另外一个事务也修改了这个表中的数据,向表中插入一行新数据。那么第一个事务在执行完之后,发现也还有未修改的数据行,就好像发生幻觉一样。
三、MVCC 用来解决什么问题
一般解决不可重复读和幻读问题,是采用锁机制实现,有没有一种乐观锁的问题去处理,可以采用 MVCC 机制的设计,可以用来解决这个问题。取代行锁,降低系统开销。
- 读写之间阻塞的问题,通过 MVCC 可以让读写互相不阻塞,读不相互阻塞,写不阻塞读,这样可以提升数据并发处理能力。
- 降低了死锁的概率,这个是因为 MVCC 采用了乐观锁的方式,读取数据时,不需要加锁,写操作,只需要锁定必要的行。
- 解决了一致性读的问题,当我们朝向某个数据库在时间点的快照是,只能看到这个时间点之前事务提交更新的结果,不能看到时间点之后事务提交的更新结果。
四、快照读与当前读
快照读,读取的是快照数据,不加锁的简单 Select 都属于快照读
SELECT * FROM table WHERE ...
当前读就是读的是最新数据,而不是历史的数据,加锁的 SELECT,或者对数据进行增删改都会进行当前读。
SELECT * FROM table LOCK IN SHARE MODE;
SELECT FROM table FOR UPDATE;
INSERT INTO table values ...
DELETE FROM table WHERE ...
UPDATE table SET ...
五、MVCC实现原理
MVCC的实现是通过保存数据在某个时间点的快照来实现的。也就是任何时刻,每个事务看到的数据都是一致的。
MVCC只在可重复读(repeatable-read) 和 已提交读(read-committed)这俩种隔离级别下适用。
MVCC实现原理是通过 隐藏字段(创建时版本号、回滚指针)、undo log 、Read view来实现的。
事务版本号
每开启一个日志,都会从数据库中获得一个事务ID(也称为事务版本号),这个事务 ID 是自增的,通过 ID 大小,可以判断事务的时间顺序。
行记录的隐藏列
- trx_id: 操作这个数据事务 ID ,也就是最后一个对数据插入或者更新的事务 ID 。
- roll_pointer: 回滚指针,指向这个记录的 Undo Log 信息。
Undo Log
undo log可以理解成回滚日志,它存储的是老版本数据。在表记录修改之前,会先把原始数据拷贝到undo log里,如果事务回滚,即可以通过undo log来还原数据。或者如果当前记录行不可见,可以顺着undo log链找到满足其可见性条件的记录行版本。
undo log作用
- 事务回滚时,保证原子性和一致性。
- 如果当前记录行不可见,可以顺着undo log链找到满足其可见性条件的记录行版本(用于MVCC快照读)。
Read View
如果一个事务要查询行记录,需要读取哪个版本的行记录呢? Read View 就是来解决这个问题的。Read View 可以帮助我们解决可见性问题。 Read View 保存了当前事务开启时所有活跃的事务列表。换个角度,可以理解为: Read View 保存了不应该让这个事务看到的其他事务 ID 列表。
- trx_ids 系统当前正在活跃的事务ID集合。
- low_limit_id ,活跃事务的最大的事务 ID。
- up_limit_id 活跃的事务中最小的事务 ID。
- creator_trx_id,创建这个 ReadView 的事务ID。
访问某条记录的时候如何判断该记录是否可见,具体规则如下:
- 如果被访问版本的 事务ID = creator_trx_id,那么表示当前事务访问的是自己修改过的记录,那么该版本对当前事务可见;
- 如果被访问版本的 事务ID < up_limit_id,那么表示生成该版本的事务在当前事务生成 ReadView 前已经提交,所以该版本可以被当前事务访问。
- 如果被访问版本的 事务ID > low_limit_id 值,那么表示生成该版本的事务在当前事务生成 ReadView 后才开启,所以该版本不可以被当前事务访问。
- 如果被访问版本的 事务ID在 up_limit_id和m_low_limit_id 之间,那就需要判断一下版本的事务ID是不是在 trx_ids 列表中,如果在,说明创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被访问。
查询一条记录
- 1、获取事务自己的版本号,即 事务ID
- 2、获取 Read View
- 3、查询得到的数据,然后 Read View 中的事务版本号进行比较。
- 4、如果不符合 ReadView 规则, 那么就需要 UndoLog 中历史快照;
- 5、最后返回符合规则的数据
MVCC实现已提交读和可重复读
- 如果事务隔离级别是 ReadCommit ,一个事务的每一次 Select 都会去查一次ReadView ,每次查询的Read View 不同,就可能会造成不可重复读或者幻读的情况。
- 如果事务的隔离级别是可重读,为了避免不可重读读,一个事务只在第一次 Select 的时候会获取一次Read View ,然后后面索引的Select 会复用这个 ReadView.