腾讯面试:MySQL事务与MVCC如何实现的隔离级别?( 三 )


MVCC 解决数据丢失MVCC , 多版本的并发控制 , Multi-Version Concurrency Control 。
使用版本来控制并发情况下的数据问题 , 在B事务开始修改账户且事务未提交时 , 当A事务需要读取账户余额时 , 此时会读取到B事务修改操作之前的账户余额的副本数据 , 但是如果A事务需要修改账户余额数据就必须要等待B事务提交事务 。
MVCC使得数据库读不会对数据加锁 , 普通的SELECT请求不会加锁 , 提高了数据库的并发处理能力 。借助MVCC , 数据库可以实现READ COMMITTED , REPEATABLE READ等隔离级别 , 用户可以查看当前数据的前一个或者前几个历史版本 , 保证了ACID中的I特性(隔离性) 。
InnoDB的MVCC实现逻辑InnoDB存储引擎保存的MVCC的数据InnoDB的MVCC是通过在每行记录后面保存两个隐藏的列来实现的 。 一个保存了行的事务ID(DB_TRX_ID) , 一个保存了行的回滚指针(DB_ROLL_PT) 。 每开始一个新的事务 , 都会自动递增产 生一个新的事务id 。 事务开始时刻的会把事务id放到当前事务影响的行事务id中 , 当查询时需要用当前事务id和每行记录的事务id进行比较 。
下面看一下在REPEATABLE READ隔离级别下 , MVCC具体是如何操作的 。
SELECT
InnoDB 会根据以下两个条件检查每行记录:

  1. InnoDB只查找版本早于当前事务版本的数据行(也就是 , 行的事务编号小于或等于当前事务的事务编号) , 这样可以确保事务读取的行 , 要么是在事务开始前已经存在的 , 要么是事务自身插入或者修改过的 。
  2. 删除的行要事务ID判断 , 读取到事务开始之前状态的版本 , 只有符合上述两个条件的记录 , 才能返回作为查询结果 。
INSERT
InnoDB为新插入的每一行保存当前事务编号作为行版本号 。
DELETE
InnoDB为删除的每一行保存当前事务编号作为行删除标识 。
UPDATE
InnoDB为插入一行新记录 , 保存当前事务编号作为行版本号 , 同时保存当前事务编号到原来的行作为行删除标识 。
保存这两个额外事务编号 , 使大多数读操作都可以不用加锁 。 这样设计使得读数据操作很简单 , 性能很好 , 并且也能保证只会读取到符合标准的行 。 不足之处是每行记录都需要额外的存储空间 , 需要做更多的行检查工作 , 以及一些额外的维护工作 。
MVCC只在REPEATABLE READ和READ COMMITIED两个隔离级别下工作 。 其他两个隔离级别都和 MVCC不兼容, 因为READ UNCOMMITIED总是读取最新的数据行 , 而不是符合当前事务版本的数据行 。 而SERIALIZABLE则会对所有读取的行都加锁 。
MVCC 在mysql 中的实现依赖的是 undo log 与 read view。
undo log根据行为的不同 , undo log分为两种: insert undo log 和 update undo log
  • insert undo log:
insert 操作中产生的undo log , 因为insert操作记录只对当前事务本身课件 , 对于其他事务此记录不可见 , 所以 insert undo log 可以在事务提交后直接删除而不需要进行purge操作 。
purge的主要任务是将数据库中已经 mark del 的数据删除 , 另外也会批量回收undo pages
数据库 Insert时的数据初始状态:
腾讯面试:MySQL事务与MVCC如何实现的隔离级别?文章插图
  • update undo log: update 或 delete 操作中产生的 undo log 。因为会对已经存在的记录产生影响 , 为了提供 MVCC机制 , 因此update undo log 不能在事务提交时就进行删除 , 而是将事务提交时放到入 history list 上 , 等待 purge 线程进行最后的删除操作 。数据第一次被修改时:
当另一个事务第二次修改当前数据:
腾讯面试:MySQL事务与MVCC如何实现的隔离级别?文章插图