当前位置: 首页 > 后端技术 > Java

MysqlMVCC机制详解

时间:2023-04-02 09:24:14 Java

什么是MVCMVCC,全称Multi-VersionConcurrencyControl,即多版本并发控制。MVCC是一种并发控制的方法。一般在数据库管理系统中,都是实现对数据库的并发访问,在编程语言中实现事务存储。我们知道,一般情况下,我们在使用mysql数据库的时候,都会使用到Innodb存储引擎。Innodb存储引擎支持事务,所以当多个线程同时执行事务时,可能会出现并发问题。这时候就需要一个可以控制并发的方法,MVCC就起到了这个作用。Mysql锁和事务隔离级别在了解MVCC机制原理之前,需要了解Mysql锁机制和事务隔离级别。除了MyISAM存储引擎,就Innodb存储引擎而言,还有行锁和表锁。有两种类型的锁。表锁在一次操作中锁定整个表。这种锁的粒度最大,但是性能也是最低的,不会出现死锁。行锁是在一次操作中锁定一行,所以锁的粒度小,并发度高,但是会出现死锁。Innodb的行锁分为共享锁(读锁)和排它锁(写锁)。当一个事务对一行加读锁时,允许其他事务读该行,但不允许写操作,其他事务不允许对该行进行写锁,但是可以加读锁。当一个事务对某一行加了写锁后,其他事务不允许对该行进行写操作,但是可以读,也不允许其他事务对这一行加读写锁。我们来看看Mysql的事务隔离级别,分为以下四种:读未提交:一个事务可以读取其他事务未提交的数据,会出现脏读。比如有一张薪水表,先开启事务A,然后执行id为1的员工的薪水,假设此时的薪水为1000,此时也开启了事务B,一个进行了update操作,id为1的员工工资减了100,但是事务没有提交。此时如果再次执行事务A的查询操作,就可以读取到事务B更新后的数据。如果此时事务B回滚,事务A读取的是“脏”数据。当事务A执行更新操作时,也可能出现幻读。Readcommitted:一个事务只能读取另一个已经提交的事务修改的数据,其他事务每次修改和提交数据,该事务都可以查询得到最新的值。还是同样的例子,这次事务隔离级别是readcommitted,如果事务B不提交事务,事务A就无法读取到事务B的更新数据,从而避免了脏数据的产生。但是事务B提交后,事务A再次执行同样的数据,会发现数据变了。这就是所谓的不可重复读。读书还在。可重复读:一个事务第一次读取一条记录后,即使其他事务修改了该记录的值并提交,该事务后面读取该记录时,仍然读取第一次读取的值,而不是每次读取不同的数据,这就是可重复读取。这种隔离级别解决了不可重复,但是还是会出现幻读。序列化:因为这种隔离级别是对同一条记录串行操作,所以不会出现脏读和幻读,但这不是并发事务。Mysql的undologMVCC底层依赖Mysql的undolog。undolog记录了数据库的操作。因为undolog是一个逻辑日志,可以理解为当一条记录被删除时,undolog会记录一条对应的insert记录,而当一条记录被update时,undolog会记录一条相反的update记录。当事务失败需要回滚时,可以通过读取undolog中相应的内容来回滚。MVCC使用撤消日志。MVCC的实现原理MVCC的实现使用了数据库的隐式字段,undolog和ReadView。首先看隐式字段。其实mysql在表中的每一行后面都隐含的记录了DB_TRX_ID(最近修改(修改/插入)的事务ID)和DB_ROLL_PTR(回滚指针,指向这条记录的上一行)。一个版本),DB_ROW_ID(自增ID,如果数据表没有主键,默认使用这个ID恢复聚簇索引),这些隐藏字段。undolog有两种类型,即insertundolog。插入新记录时产生的undolog只有在事务回滚时才需要,事务提交后可以立即丢弃。还有updateundolog,事务在更新或删除时产生的undolog,不仅在事务回滚时需要,在读取快照时也需要;所以不能随便删除,只有当日志不涉及快读或者事务回滚线程时才会清除对应的日志,统一清除。MVCC使用更新撤消日志。其实undolog记录的是一个版本链。假设数据库中有一条记录如下。事务A首先对行记录加行锁,然后将行记录复制到undolog。将行复制为老版本后,将该行的名字改为tom,然后将该行的DB_TRX_ID的值改为事务A的id。此时,假设事务A的id为1,则DB_POLL_PTR行的指向复制到撤消日志的记录。事务提交后,释放锁此时的情况是这样的:此时又有事务B修改了这条记录,将age修改为28,此时的操作过程为:事务B添加了一个对修改后的记录进行行锁定,并将行记录复制到undolog。作为老版本,发现此时已经记录了undolog,则在该行记录的undolog最前面插入一个新的undolog作为链表的头部。复制完成后,该行的age改为28。然后将该行的DB_TRX_ID的值改为事务B的id。此时假设事务B的id为2,则该行的DB_POLL_PTR指向将记录复制到撤消日志。事务提交后,释放锁,情况如下:从上面我们可以看出,不同的事务或者同一个事务修改同一行记录,都会使得该行记录的undolog形成一个版本链。undolog链的头部是最新的oldrecord,链尾是最早的oldrecord。现在让我们假设一种情况。假设事务A和事务B都没有提交。这时有一个事务C,修改了名为tom的记录,将age修改为30,然后提交事务。事务C的id为3,同样会在undolog中插入一条记录。此时undolog版本链中第一条记录的DB_TRX_ID为3,现在有一个事务D,查询名字为tom的记录。此时会启用快照读取。快照是事务开始时由查询操作触发的数??据快照。readwithoutlock是repeatableread隔离级别下默认的snapshotread,相对于snapshotread,还有一种叫做currentread,update操作都是currentread。读取快照时会生成读取视图(Readview)。在事务执行快照读取的那一刻,会生成一个数据库的当前快照,记录并维护当前活跃事务的ID,因为事务的ID是自增的。是的,所以越新的交易ID越大。读视图遵循可见性算法,是否可见需要一些判断。读取视图除了记录当前活跃的事务ID外,还记录了当前创建的最大事务ID。在读取快照时,需要将其与读取视图进行比较,以获得可见性结果。读视图主要是将当前事务的ID与系统中活跃事务的ID进行比较。比较规则如下:首先在Read视图中生成Read视图时会有一个系统中活跃的事务ID数组,暂称为id_list,然后Read视图将最小的事务ID记录在id_list中,暂时称为low_id。最后,Readview在生成Readview时,还会记录一个尚未在系统中分配的事务ID,即当前最大的事务ID+1,暂时称为high_id。如果当前交易ID小于low_id,则当前交易可见。如果当前交易ID大于high_id,则当前交易不可见。当前事务大于low_id小于high_id,再判断是否在id_list中。如果是,说明活跃事务还没有提交,当前事务是不可见的,但是对活跃事务本身是可见的。如果不在id_list中,则当前交易可见。如果可见性结果不可见,则需要通过DB_ROLL_PTR将记录的DB_TRX_ID与undolog进行比较,遍历Version链,直到找到满足一定条件的DB_TRX_ID,则DB_TRX_ID所在的old记录为最新的old当前事务可以看到的版本。