写点什么

给你讲懂 MVCC

作者:Nick
  • 2022 年 6 月 23 日
  • 本文字数:2077 字

    阅读完需:约 7 分钟

给你讲懂 MVCC

很久以前,有位面试官问到,你知道 mysql 的事务隔离级别吗,“额 O__O …,不太清楚”,完了之后我就去网上找相关的文章,找到了这篇MySQL 四种事务隔离级的说明, 文章写得特别好,看了这个就懂了各个事务隔离级别都是啥,不过看了这个之后多思考一下的话还是会发现问题,这么神奇的事务隔离级别是怎么实现的呢


其中 innodb 的事务隔离用到了标题里说到的 mvcc,Multiversion concurrency control, 直译过来就是多版本并发控制,先不讲这个究竟是个啥,考虑下如果纯猜测,这个事务隔离级别应该会是怎么样实现呢,愚钝的我想了下,可以在事务开始的时候拷贝一个表,这个可以支持 RR 级别,RC 级别就不支持了,而且要是个非常大的表,想想就不可行


腆着脸说虽然这个不可行,但是思路是对的,具体实行起来需要做一系列(肥肠多)的改动,首先根据我的理解,其实这个拷贝一个表是变成拷贝一条记录,但是如果有多个事务,那就得拷贝多次,这个问题其实可以借助版本管理系统来解释,在用版本管理系统,git 之类的之前,很原始的可能是开发完一个功能后,就打个压缩包用时间等信息命名,然后如果后面要找回这个就直接用这个压缩包的就行了,后来有了 svn,git 中心式和分布式的版本管理系统,它的一个特点是粒度可以控制到文件和代码行级别,对应的我们的 mysql 事务是不是也可以从一开始预想的表级别细化到行的级别,可能之前很多人都了解过,数据库的一行记录除了我们用户自定义的字段,还有一些额外的字段,去源码data0type.h里捞一下


/* Precise data types for system columns and the length of those columns;NOTE: the values must run from 0 up in the order given! All codes mustbe less than 256 */#define DATA_ROW_ID 0     /* row id: a 48-bit integer */#define DATA_ROW_ID_LEN 6 /* stored length for row id */
/** Transaction id: 6 bytes */constexpr size_t DATA_TRX_ID = 1;
/** Transaction ID type size in bytes. */constexpr size_t DATA_TRX_ID_LEN = 6;
/** Rollback data pointer: 7 bytes */constexpr size_t DATA_ROLL_PTR = 2;
/** Rollback data pointer type size in bytes. */constexpr size_t DATA_ROLL_PTR_LEN = 7;
复制代码


一个是 DATA_ROW_ID,这个是在数据没指定主键的时候会生成一个隐藏的,如果用户有指定主键就是主键了


一个是 DATA_TRX_ID,这个表示这条记录的事务 ID


还有一个是 DATA_ROLL_PTR 指向回滚段的指针


指向的回滚段其实就是我们常说的 undo log,这里面的具体结构就是个链表,在 mvcc 里会使用到这个,还有就是这个 DATA_TRX_ID,每条记录都记录了这个事务 ID,表示的是这条记录的当前值是被哪个事务修改的,下面就扯回事务了,我们知道 Read Uncommitted, 其实用不到隔离,直接读取当前值就好了,到了 Read Committed 级别,我们要让事务读取到提交过的值,mysql 使用了一个叫 read view 的玩意,它里面有这些值是我们需要注意的,


m_low_limit_id, 这个是 read view 创建时最大的活跃事务 id


m_up_limit_id, 这个是 read view 创建时最小的活跃事务 id


m_ids, 这个是 read view 创建时所有的活跃事务 id 数组


m_creator_trx_id 这个是当前记录的创建事务 id


判断事务的可见性主要的逻辑是这样,


  1. 当记录的事务 id 小于最小活跃事务 id,说明是可见的,

  2. 如果记录的事务 id 等于当前事务 id,说明是自己的更改,可见

  3. 如果记录的事务 id 大于最大的活跃事务 id, 不可见

  4. 如果记录的事务 id 介于 m_low_limit_idm_up_limit_id 之间,则要判断它是否在 m_ids 中,如果在,不可见,如果不在,表示已提交,可见具体的代码捞一下看看


/** Check whether the changes by id are visible.  @param[in]  id  transaction id to check against the view  @param[in]  name  table name  @return whether the view sees the modifications of id. */  bool changes_visible(trx_id_t id, const table_name_t &name) const      MY_ATTRIBUTE((warn_unused_result)) {    ut_ad(id > 0);
if (id < m_up_limit_id || id == m_creator_trx_id) { return (true); }
check_trx_id_sanity(id, name);
if (id >= m_low_limit_id) { return (false);
} else if (m_ids.empty()) { return (true); }
const ids_t::value_type *p = m_ids.data();
return (!std::binary_search(p, p + m_ids.size(), id)); }
复制代码


剩下来一点是啥呢,就是 Read CommittedRepeated Read 也不一样,那前面说的 read view 都能支持吗,又是怎么支持呢,假如这个 read view 是在事务一开始就创建,那好像能支持的只是 RR 事务隔离级别,其实呢,这是通过创建 read view的时机,对于 RR 级别,就是在事务的第一个 select 语句是创建,对于 RC 级别,是在每个 select 语句执行前都是创建一次,那样就可以保证能读到所有已提交的数据


本文使用署名 4.0 国际 (CC BY 4.0)许可协议,欢迎转载、或重新修改使用,但需要注明来源。

本文作者: Nicksxs

创建时间: 2020-04-26

本文链接: 给你讲懂 MVCC

发布于: 刚刚阅读数: 3
用户头像

Nick

关注

还未添加个人签名 2017.12.22 加入

写代码的阿森 https://nicksxs.me https://nicksxs.com 也可以看我的博客

评论

发布
暂无评论
给你讲懂 MVCC_MySQL_Nick_InfoQ写作社区