闲聊MySQL:(三)深入分析InnoDB之多版本控制MVCC

前言

在上一篇中,我们介绍了MySQL的核心存储引擎InnoDB,对其特性进行了简单的介绍,本篇,我们继续对InnoDB进行分析,了解一下其内部的重要的机制之一,多版本控制MVCC。

InnoDB多版本控制(MVCC)

什么是多版本控制(MVCC)?

多版本控制指的是一种提高并发的技术。最早的数据库系统,只有读读之间可以并发,读写,写读,写写都要阻塞。引入多版本之后,只有写写之间相互阻塞,其他三种操作都可以并行,这样大幅度提高了InnoDB的并发度。

InnoDB的一致性的非锁定读就是通过在MVCC实现的,MySQL的大多数事务型存储引擎实现的都不是简单的行级锁。基于提升并发性能的考虑,它们一般都同时实现了多版本并发控制(MVCC)。MVCC的实现,是通过保存数据在某一个时间点的快照来实现的。因此每一个事务无论执行多长时间看到的数据,都是一样的。所以MVCC实现可重复读。

  • 快照读:select语句默认,不加锁,MVCC实现可重复读,使用的是MVCC机制读取undo中的已经提交的数据。所以它的读取是非阻塞的。
  • 当前读:select语句加S锁或X锁;所有的修改操作加X锁,在select for update 的时候,才是当地前读。

InnoDB的内部实现

在InnoDB中,会保存有关已更改行的旧版本信息,以支持并发和回滚事务等功能。InnoDB MVCC的实现基于Undo log,它将该信息存储在表空间中的一个回滚段的数据结构(Oracle中也有类似的数据结构)。

InnoDB 使用回滚段中的信息来执行事务回滚中所需的撤销操作。

Undo log

Undo log可以用来做事务的回滚操作,保证事务的原子性。同时可以用来构建数据修改之前的版本,支持多版本读。

InnoDB表数据组织方式是主键聚簇索引。二级索引通过索引键值加主键值组合来唯一确定一条记录。

一个表只能有一个主键,所以只能有一个聚簇索引,如果表没有定义主键,则选择第一个非NULL唯一索引作为聚簇索引,如果还没有则生成一个隐藏id列作为聚簇索引。

在内部实现中,InnoDB为数据库中存储的每一行添加三个字段。

第一部分,6个字节的DB_TRX_ID字段表明插入或更新行的最后一个事务的事务标识符。此外,删除在内部被视为更新,其中行中的特殊位被设置为将其标记为已删除。

第二部分,7个字节的DB_ROLL_PTR字段表示回滚指针。滚动指针指向写入undo segment回滚段的undo log撤销日志。如果这行数据被更新了,则undo log记录包含在更新之前的数据。

第三部分,6个字节的DB_ROW_ID字段包含一个ROW ID,如果你对Oracle熟悉,一点对其不陌生,当插入新的数据行时,该ID会自动递增,如果 InnoDB自动生成主键聚簇索引,则其包含ROW ID值。否则,该 DB_ROW_ID列不会出现在任何索引中。

Undo log 类别

撤销段中的undo log分为INSERT和UPDATE两种。delete可以看做是一种特殊的update,即在记录上修改删除标记。

  • INSERT:当进行插入数据操作时,会生成Insert undo log,只在事务回滚时需要, 并且在事务提交后就可以立即丢弃。
  • UPDATE/DELETE:事务对记录进行delete和update操作时产生undo log,并将当前数据记录中的DB_ROLL_PTR字段指向它,它不仅在事务回滚时需要,一致性读也需要,所以不能随便删除,只有当数据库所使用的快照中不涉及该日志记录,对应的回滚日志才会被purge线程删除。
注意事项

在执行SQL时,应当注意定期提交事务,包括那些只发出一致性读取的事务。否则,InnoDB无法从update undo log中丢弃数据,并且回滚段可能会变得太大,从而填满了表空间。

回滚段中撤销日志记录的物理大小通常小于相应的插入或更新行。可以使用此信息计算回滚段所需的空间。

在InnoDB多版本控制方案中,使用SQL语句删除行时,不会立即从数据库中物理删除该行。InnoDB真正的删除在事务commit之后且没有读会引用该版本数据的时候,才会物理删除相应的行及其索引记录。此删除操作称为清除,并且速度非常快,通常与执行删除的SQL语句的时间顺序相同。

如果你在表中以大约相同的速率插入和删除少量批次的记录行,则清除线程可能开始落后,并且由于所有的“死”行,表可以变得越来越大,使得所有磁盘都受到限制慢。

在这种情况下,通过调整innodb_max_purge_lag 系统变量参数,限制新的行操作,并将更多资源分配给清除线程。

MVCC与二级索引

InnoDB多版本并发控制(MVCC)以不同于聚簇索引的方式处理二级索引。对于聚簇索引,更新是在原记录位置更新,通过记录指向undo log的隐藏列来重构早期版本的数据。

但对于二级索引,是没有聚簇索引上的这些隐藏列的,因此无法在原记录位置更新。

当二级索引更新的时候,需要将原记录标记为删除,再插入新的数据记录。当快照读通过二级索引读取数据发现deleted标识或者更新的时候,如果二级索引页上无法判断可见性,InnoDB会查看聚簇索引上的记录行,通过行上的DB_TRX_ID判断可见性,找到正确的可见版本数据。

MVCC如何实现Repeatable Read(RR可重复读)

我们在上一篇提到过,InnoDB中的默认隔离级别是Repeatable Read(可重复读),即在一个事务内同一快照读执行任意次数,得到的数据一致;且只能读到第一次执行前已经提交的数据或本事务内更改的数据。

其实现机制主要基于read view快照,对符合查询条件的记录进行可见性判断就是那些数据本事务可以看见,那些数据看不见),当开启事务后,第一次执行select操作会创建快照,快照是基于执行第一个读操作的时间。

这样做的好处是,在同一事物中可以保证重复读,但不能完全避免幻读。

但在Read Committed(读已提交)下,事务能够看到其他事务提交后的修改,每次读操作都会创建新的快照,即为不可重复读。

总结

本篇,我们介绍了InnoDB的MVCC多版本控制机制的内部实现,InnoDB的MVCC主要基于undo log进行实现的。

在事务隔离级别为Repeatable Read和Read Committed级别下, InnnoDB存储引擎使用的是MVCC机制。然而,对于快照数据的定义却不相同。

在RC事务隔离级别下,对于快照数据(undo端数据),总是读取被锁定行的最新的一份快照数据。而在RR事务隔离级别下,对于快照数据,多版本并发控制总是读取事务开始时的行数据。

在下一篇中,我们将会继续深入InnoDB,去了解一下InnoDB的内存结构模型,敬请期待!

更多精彩文章, 请关注我的个人公众号:老宣与你聊Java
让我们一起共同学习成长!
感谢您的支持!

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值