一文了解MVCC——解锁数据库并发控制的“魔法钥匙”

MVCC概念

MVCC全称叫做Multiversion Concurrency Control,多版本并发控制。MVCC的出现主要是为了提升数据库并发性能,用较好的方式处理事务并发的读写冲突,避免了加锁操作,降低性能开销,在有读写冲突时,能够做到非阻塞并发读。

  • MVCC可以通过乐观锁的方式,在可重复读隔离级别下来解决不可重复读和幻读的问题。

MVCC原理

在MVCC机制中,多个事务对同一条记录做修改,会产生多个历史快照,这些历史快照保存在undo log里,当一个事务对该记录进行查询时,MVCC会通过ReadView解决行的可见性问题。

ReadView是事务在使用MVCC机制进行快照读时产生的读视图。

ReadView

ReadView字段说明:

字段 字段说明
creator_trx_id 创建这个 Read View 的事务 ID
trx_ids 生成ReadView时当前系统中活跃的 事务id列表
up_limit_id 活跃的事务中最小的事务 ID
low_limit_id 系统最大的事务ID值(注意,不是活跃事务中的最大值)

undolog回滚链

  • mysql数据库中,最新的记录永远是行记录。行记录中的回滚指针指向的是上一个修改该数据的事务对应的undo log,当判断一个事务要读取哪个版本时,需要根据最新的行记录按回滚链结合undolog进行回滚,来得到历史快照,而不是直接存放了多个行记录。 如果每一个事务启动时都创建一个整张表的快照,那显然不现实,假如一个表50G,创建个50G的快照肯定就超级慢了。

假如这里有一张book表,表里有三个字段主键idbook_nameuserbook_name为书名,user为当前借阅该书人的姓名。表里初始数据如下:

id book_name user
1 《活着》 小赵
有两条事务分别对这条数据进行如下操作:
事务5 事务10
BEGIN;
BEGIN;
update book set user = '小钱' where id = 1
update book set user = '小孙' where id = 1
COMMIT;
update book set user = '小李' where id = 1
update book set user = '小周' where id = 1
COMMIT;
则上述两个事务操作会形成如下表所示的版本链:
版本标识 id book_name user trx_id roll_pointer
V5 1 《活着》 小周 10 V4
V4 1 《活着》 小李 10 V3
V3 1 《活着》 小孙 5 V2
V2 1 《活着》 小钱 5 V1
V1 1 《活着》 小赵 1

ReadView 规则

当有事务X 查询id = 1这条数据时,会按照什么规则进行判断读到的值呢?这里假设要查询的事务为X,数据版本对应的事务为D, 当进行查询时,活跃的事务有事务5事务10,**即X事务此时对应的ReadViewtrx_ids值为[5,10],则在判断可见性时会按照如下规则进行判断:

  • 数据版本的的trx_idReadView中的creator_trx_id和一致,说明ReadView由当前事务X创建,所以该数据版本可以被事务X查看。
  • 数据版本的trx_id小于ReadViewup_limit_id,则说明该数据版本对应的事务已经提交,事务X能够看到该数据版本。
  • 数据版本的trx_id大于等于ReadViewlow_limit_id,说明该数据版本对应事务的提交在事务X之前,则该数据版本对事务X不可见。
  • 数据版本的trx_idReadViewup_limit_idlow_limit_id之间,则有如下两种情况:
    • 数据版本对应的trx_idReadViewtrx_ids中,说明该数据版本对应的事务事务X创建ReadView时未提交,则该数据版本对于事务X不可见。
    • 数据版本对应的trx_id不在ReadViewtrx_ids中,说明该数据版本对应的事务已经提交,所以,该数据版本对事务X可见。

上述规则在阅读起来稍微有些难以理解,下面以一个表格来辅助记忆:

< up_limit_id trx_ids:[5,10] >=low_limit_id
区间1 区间2 区间3
已提交事务集合 未提交事务集合 未开始事务集合
一个数据版本对应的trx_id有以下几种可能,这里将数据版本对应的事务称作事务V :
  • 落在区间1,表示已提交,则该数据对事务X可见
  • 落在区间3,表示事务V事务X创建时还未开始,则该数据对事务X不可见。
  • 落在区间2
    • 如果trx_id不在trx_ids列表里,表示事务V已提交,则该数据对事务X可见。
    • 如果trx_idtrx_ids集合里,表示事务V事务X创建时还未完成提交,则该数据对事务X不可见。

快照读与当前读

  • 快照读,又称为一致性读,读的是快照数据。简单的select * from t where ……读的就是快照。
  • 当前读,指读取的是最新版本,并且在读取时会对读到的记录进行加锁。加锁的 SELECT,或者对数据进行增删改(更新数据都是先读后写的)都会进行当前读。

MVCC与隔离级别

READ UNCOMMITED隔离级别下,允许一个事务可以读取到其他事务未提交的数据,因此,此种隔离级别下无需做任何特殊处理。

SERIALIZABLE隔离级别下,由于事务只能串型读取,只能采取加锁的方式实现。

READ COMMITTEDREPEATABLE READ隔离级别下,要保证读到的是已提交的数据。那么,这个时候就需要用到MVCC了。总结就是:MVCC会用于READ COMMITTEDREPEATABLE READ隔离级别的实现。

要保证REPEATABLE READ隔离级别的实现,只需要在第一次Select的时候获取一次ReadView,后面所有的Select都使用同一个ReadView即可。

要保证READ COMMITTED隔离级别的实现,就需要在每次Select的时候都重新获取一次ReadView

你可能感兴趣的:(mysql实战,数据库,mysql,java)