MySQL系列:MVCC

1. 什么是多版本并发控制

MVCC(多版本并发控制,Multiversion concurrency control),它是一种在并发时对读写控制的方法。

MVCC 是通过保存数据在某个时间点的快照来实现并发控制的。不管事务执行多长时间,事务内部看到的数据是不受其它事务影响的,根据事务开始的时间不同,每个事务对同一张表,同一时刻看到的数据可能是不一样的。

MVCC 使得各个事务在读写数据时能最大程度的降低锁的依赖,在保证事务隔离性的同时,也能让读类型的事务和写操作的事务并发进行。

1.1 MVCC 的思想

通过保存数据的历史版本,通过对数据的多个版本管理来实现数据库的并发控制。这样我们就可以通过比较版本号决定数据是否显示出来,读取数据的时候不需要加锁也可以保证事务的隔离效果。

1.2 MVCC 解决的问题

  1. 读写之间阻塞的问题
    通过 MVCC 可以让读写互相不阻塞,即读不阻塞写,写不阻塞读,这样就可以提升事务并发处理能力
  2. 降低了死锁的概率
    因为 InnoDB 的 MVCC 采用了乐观锁的方式,读取数据时并不需要加锁,对于写操作,也只锁定必要的行
  3. 解决一致性读的问题
    一致性读也被称为快照读,当我们查询数据库在某个时间点的快照时,只能看到这个时间点之前事务提交更新的结果,而不能看到这个时间点之后事务提交的更新结果

1.3 快照读与当前读

快照读(SnapShot Read) 是一种一致性不加锁的读,是InnoDB并发如此之高的核心原因之一。

  • 这里一致性指事务读取到的数据,要么是事务开始前就已经存在的数据,要么是事务自身插入或者修改过的数据

快照读
不加锁的简单的 SELECT 都属于快照读,例如:

SELECT * FROM t WHERE id=1;

当前读
当前读就是读取最新数据,而不是历史版本的数据。加锁的 SELECT 就属于当前读,例如:

SELECT * FROM t WHERE id=1 LOCK IN SHARE MODE;

SELECT * FROM t WHERE id=1 FOR UPDATE;
  • SELECT ... LOCK IN SHARE MOD:是IS锁(意向共享锁)
    • 在符合条件的rows上都加了共享锁,其他session可以读取这些记录,也可以继续添加IS锁,但是无法修改这些记录当前加锁的session执行完成或锁等待超时
  • SELECT ... FOR UPDATE :是IX锁(意向排它锁)
    • 在符合条件的rows上都加了排它锁,其他session也就无法在这些记录上添加任何的S锁或X锁

2. MVCC 原理

2.1 undo log

undo log 是对事务操作时的一个日志记录,以提供数据回滚功能。

  • 事务进行了 insert 操作,则回滚时会逆向解析为 delete
  • 事务进行了 delete 操作,则变为 insert 操作,相当于一种逻辑的反向操作

undo log 除了记录下每一次的操作类型、数据情况和事务 ID 外,还包含了上一次的记录指针,以形成完整的历史链路。

MVCC 历史版本数据就来是从 undo log 的记录指针去追溯获取的。

2.2 最近一次的记录指针

每当我们插入一行数据的时候,数据库还会额外的帮我们生成 2 个隐形字段:

  • DB_TRX_ID:事务 ID
  • DB_ROLL_PTR:回滚记录指针

每次需要追溯历史版本就从 DB_ROLL_PTR 这开始:

关于 MVCC 在 undo log 里所产生的历史版本也不会一直存着,在满足一定条件后则会被标记为清除状态,等待清理。

像 insert 类型的 undo log 则可以在提交事务后就标记为清除状态,因为这是属于新增的,不会有其他事务依赖到。

2.3 Read View

隔离性有四个隔离级别:未提交读、提交读、可重复读、可串行化。事实上 MVCC 只在已提交读和可重复读隔离级别上实现了。

  • 未提交读要求最低,只要能获取到数据就返回
  • 可串行化天生就把别的请求挡在外面了,不用考虑事务之间的并发执行

在已提交读、可重复读的隔离级别上,MVCC 也会有所不一样的,主要体现在 Read View 的生成上。Read View 是对当时所活跃的事务 id 的维护,包含的字段如下:

  • m_ids:当时正在发生的所有事务 id 集合
  • m_low_limit_id:当前事务最多能读取到的事务 id,相当于“高水位”警戒线,超过它就不能继续读了
  • m_up_limit_id:跟上面相反,属于“低水位”警戒线,所读取的事务 id 都应该比这个值大
  • m_creator_trx_id:当前的事务 id

当把这些事务 id 维护到当前事务的 Read View 里后,就可以控制其他事务对当前事务的可见性:

  • 比如当前 Read View 的 m_low_limit_id 是 10,那即使后面又有新的事务产生,当前事务也只能读取到这个 id 为 10 的事务为止,毕竟后面的事务是属于新来的
  • 比如当前事务需要回滚了,则会根据 undo log 的记录指针以及 Read View 的 m_up_limit_id 去控制回滚

提交读
对于提交读,它在每次 SELECT 的时候都会重新生成 Read View,所以已提交读在同一事务里将有可能读到不一样的提交数据。
可重复读
可重复读只在第一次 SELECT 的时候生成,所以后面读取到的数据都在此处的版本控制内。

3. 总结

MVCC 通过 undo log 的 记录指针获得了一个个的历史版本,就像镜像备份一样,使得数据的读写不必再依赖一份数据,提高了并发执行效率。

不过 undo log 清除线程比较滞后的话,将会导致 undo log 越来越大,影响磁盘操作效率,所以必要的时候可以分配更多的资源给清除线程。

posted @ 2022-03-25 00:19  当康  阅读(214)  评论(0编辑  收藏  举报