共计 2615 个字符,预计需要花费 7 分钟才能阅读完成。
作者:朱庆林
大家晓得 MySQL 中的事务是基于 MVCC 版本链实现的,然而 MySQL 对于咱们来说是一个黑盒,对于底层的实现理解的不是很多。本文次要介绍 MySQL 中的 InnoDB 引擎的 MVCC 的实现原理,由浅到深率领大家从根上了解 MySQL
InnoDB 行格局
InnoDB 存储引擎中记录是以行的模式存储的,这就意味着数据页(page)中保留的是一行行的数据,咱们把记录在磁盘上的寄存形式被称为行格局或者记录格局。到目前为止设计了 4 种不同类型的行格局,别离为 Compact、Redundant、Dynamic 和Compressed。本文只简略的介绍 Compact 行格局(其余的行格局大同小异,暂不做介绍)。能够通过下列命令批改、查看行格局
## 创立表设置行格局
CREATE TABLE 表名 (列的信息) ROW_FORMAT= 行格局名称
## 批改行格局
ALTER TABLE 表名 ROW_FORMAT= 行格局名称
## 查看表行格局
SHOW TABLE STATUS LIKE "表名"
COMPACT 行格局
上图为 compact 行格局的构造示意图,其中跟事务(MVCC)有关联的是暗藏列的内容
变长字段长度列表
mysql 反对一些变长字段类型比方:VARCHAR、TEXT、BLOB 等。变长字段中存储多少字节的数据是不固定的,所以咱们在存储实在数据的时候须要顺便把这些数据占用的字节数也存起来。
null 值列表
表中的某些列可能存储 NULL 值,如果把这些 NULL 值都放到记录的实在数据中存储会很占中央,所以 Compact 行格局把这些值为 NULL 的列对立治理起来,存储到 NULL 值列表
记录头信息
暗藏列
名称 | 形容 |
---|---|
row_id | 列 id(如果表没有指定主键,该列为暗藏主键) |
trx_id | 事务 id |
roll_pointer | 回滚指针、指向 undo 日志 |
SQL 规范中的四种隔离级别
- READ UNCOMMITTED:未提交读。
- READ COMMITTED:已提交读。
- REPEATABLE READ:可反复读。
- SERIALIZABLE:可串行化。
事务隔离级别 | 脏读 | 不可反复读 | 幻读 |
---|---|---|---|
READ UNCOMMITTED | 是 | 是 | 是 |
READ COMMITTED | 否 | 是 | 是 |
REPEATABLE READ | 否 | 否 | 是 |
REPEATABLE READ | 否 | 否 | 否 |
MVCC 原理
版本链
下面介绍过行格局中有个暗藏的列(row_id,trx_id,roll_pointer),其中 row_id 不是必须的。
- trx_id:每次一个事务对某条聚簇索引记录进行改变时,都会把该事务的事务 id 赋值给 trx_id 暗藏列。
- roll_pointer:每次对某条聚簇索引记录进行改变时,都会把旧的版本写入到 undo 日志中,而后这个暗藏列就相当于一个指针,能够通过它来找到该记录批改前的信息。
备注 : 事务执行过程中,只有在第一次真正批改记录时(比方应用 INSERT、DELETE、UPDATE 语句),才会被调配一个独自的事务 id,这个事务 id 是递增的
以后有个 hero 的表,查问后果下图:
假如插入该记录的事务 id 为 80,那么此刻该条记录的示意图如下所示
之后两个事务 id 别离为 100、200 的事务对这条记录进行 UPDATE 操作,操作流程如下:
事务 trx_id 100 | 事务 trx_id 200 |
---|---|
begin | |
begin | |
UPDATE hero set name=” 关羽 ” | |
UPDATE hero set name=” 张飞 ” | |
commit | |
UPDATE hero set name=” 赵云 ” | |
UPDATE hero set name=” 诸葛亮 ” | |
commit |
此时的版本链就如下图所示,能够看到记录的批改组成了一个链表,链表中每个节点都记录了以后记录的 事务 id(trx_id),MVCC 也是基于这些链表去实现的事务级别的 4 种隔离级别,也就是上面介绍的ReadView。
ReadView
对于应用 READ UNCOMMITTED 隔离级别的事务来说,因为能够读到未提交事务批改过的记录,所以间接读取记录的最新版本就好了;对于应用 SERIALIZABLE 隔离级别的事务来说,规定应用加锁的形式来拜访记录;对于应用 READ COMMITTED 和REPEATABLE READ隔离级别的事务来说,都必须保障读到曾经提交了的事务批改过的记录,也就是说如果另一个事务曾经批改了记录然而尚未提交,是不能间接读取最新版本的记录的,外围问题就是:须要判断一下版本链中的哪个版本是以后事务可见的。为此 mysql 设计出了 ReadView 的概念,ReadView中有 4 个比拟重要的属性:
- m_ids:示意在生成 ReadView 时以后零碎中沉闷的读写事务的事务 id 列表。
- min_trx_id:示意在生成 ReadView 时以后零碎中沉闷的读写事务中最小的事务 id,也就是 m_ids 中的最小值。
- max_trx_id:示意生成 ReadView 时零碎中应该调配给下一个事务的 id 值。
- creator_trx_id:示意生成该 ReadView 的事务的事务 id。
有了这个 ReadView,这样在拜访某条记录时,只须要依照下边的步骤判断记录的某个版本是否可见:
- 如果被拜访版本的 trx_id 属性值与 ReadView 中的 creator_trx_id 值雷同,意味着以后事务在拜访它本人批改过的记录,所以该版本能够被以后事务拜访。
- 如果被拜访版本的 trx_id 属性值小于 ReadView 中的 min_trx_id 值,表明生成该版本的事务在以后事务生成 ReadView 前曾经提交,所以该版本能够被以后事务拜访。
- 如果被拜访版本的 trx_id 属性值大于或等于 ReadView 中的 max_trx_id 值,表明生成该版本的事务在以后事务生成 ReadView 后才开启,所以该版本不能够被以后事务拜访。
- 如果被拜访版本的 trx_id 属性值在 ReadView 的 min_trx_id 和max_trx_id之间,那就须要判断一下 trx_id 属性值是不是在 m_ids 列表中,如果在,阐明创立 ReadView 时生成该版本的事务还是沉闷的,该版本不能够被拜访;如果不在,阐明创立 ReadView 时生成该版本的事务曾经被提交,该版本能够被拜访。
基于下面的 ReadView 的规定,READ COMMITTED和 REPEATABLE READ 有什么不同呢?
-
READ COMMITTED —— 每次读取数据前都生成一个 ReadView
-
READ COMMITTED —— 在第一次读取数据时生成一个 ReadView
参考资料:
MySQL 技术底细
MySQL 是怎么运行的