仅用于自己理解MVCC的概念
先介绍几个MySQL相关的概念
read-view
这个概念在很多的文章中都出现过,特别是在讲MVCC
,一致性读概念的时候,但是在MySQL的官方文档中确没有发现(待更新),准确的说是Reference Manual中没有发现。唯一在官方些的文档中发现read-view
这个字眼是MySQL的源码文档https://dev.mysql.com/doc/dev/mysql-server/9.0.1/classReadView.html#details
Read view lists the trx ids of those transactions for which a consistent read should not see the modifications to the database.
也就是说,我列出了一些修改记录的事务的ID,你一致性读就别想读这些内容了
看下面源码的注释,大家在不同文章中说的 consistent read view
、consistent snapshot
、一致性读视图
其实说的都是一个东西,这里就以read-view
称呼
struct read_view_struct{
ulint type; /*!< VIEW_NORMAL, VIEW_HIGH_GRANULARITY */
undo_no_t undo_no;/*!< 0 or if type is
VIEW_HIGH_GRANULARITY
transaction undo_no when this high-granularity
consistent read view was created */
如下是read-view
包含的一些字段
private:
/** The read should not see any transaction with trx id >= this
value. In other words, this is the "high water mark". */
trx_id_t m_low_limit_id;
/** The read should see all trx ids which are strictly
smaller (<) than this value. In other words, this is the
low water mark". */
trx_id_t m_up_limit_id;
/** trx id of creating transaction, set to TRX_ID_MAX for free
views. */
trx_id_t m_creator_trx_id;
/** Set of RW transactions that was active when this snapshot
was taken */
ids_t m_ids;
/** The view does not need to see the undo logs for transactions
whose transaction number is strictly smaller (<) than this value:
they can be removed in purge if not needed by other views */
trx_id_t m_low_limit_no;
m_ids
:快照开始时活跃事务的ID列表;
m_up_limit_id
:如果事务ID小于这个值的话,那么当前事务是可以看到的。up_limit_id
也就是这批活跃事务列表的最小值;
m_low_limit_id
:如果事务ID大于等于这个值的话,那么当前事务都看不到。low_limit_id
也就是这批活跃事务列表的最大值 + 1;
还有一个相关的id需要介绍DB_TRX_ID
,MySQL官网在14.3 InnoDB Multi-Versioning这一节是这么介绍它的
Internally, InnoDB adds three fields to each row stored in the database:
A 6-byte DB_TRX_ID field indicates the transaction identifier for the last transaction that inserted or updated the row. Also, a deletion is treated internally as an update where a special bit in the row is set to mark it as deleted.
也就是说用来标记最后一个修改记录的事务ID
那么这个read-view是何时生成的?这里直接引用极客时间MySQL实战45讲里的一段话
begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个操作 InnoDB 表的语句,事务才真正启动。如果你想要马上启动一个事务,可以使用 start transaction with consistent snapshot 这个命令。
第一种启动方式,一致性视图是在执行第一个快照读语句时创建的;
第二种启动方式,一致性视图是在执行 start transaction with consistent snapshot 时创建的。
概念的准备工作基本完成
假设,现在有个事务启动了(MySQL默认隔离级别,RR),而且使用的是start transaction with consistent snapshot
,那么一开始就会有这个read-view
的创建。
假设现在活跃的事务列表为[20,25, 30]
现在这个事务要开始查询某个记录
1、假设现在记录的DB_TRX_ID为10,10 < 20,那么表示记录在read-view创建前就已经更新了,可以访问,人家都提交了,都比现在的活跃事务列表小了
2、假设现在记录的DB_TRX_ID为40,40 > 30 + 1,那么表示是在read-view创建后才更新了,当然不可以访问。那我现在就想读取数据怎么办呢?现在这个版本是我不能读的(有那么一点MVCC
多版本的味道了),这时候就得去undo log拿了,为何去undo log拿?
If the row was updated, the undo log record contains the information necessary to rebuild the content of the row before it was updated.
不同的事务在启动的时候,我拿到的read-view是不同的,也就是我能看到的内容是不同的,一个值可能被从1改到了10。我启动的时候可能当时就是3,但是人家其他事务改了,我是看不到的,我就能看老的。那老的怎么看呢?就得靠undo log了,毕竟人家人家特性摆在那里呢
接着上面“DB_TRX_ID为40”继续,最新的不行,那我就要去undo log取旧的。怎么取呢?和刚才的步骤其实是一样的。在记录每次被修改的时候,是有字段记录了是谁修改了这个记录,也就是修改的事务ID,一直比较,直到最后找到
3、假设现在记录的DB_TRX_ID,m_up_limit_id <= DB_TRX_ID < low_limit_id` ,也就是DB_TRX_ID位于活跃事务列表的区间。如果此时活跃列表真的有DB_TRX_ID,那么还是不可见的,毕竟你还在活跃嘛,如果不在的话,那可以的,毕竟提交了嘛
回到最初的问题,为何长事务要避免。上面的read-view,在读取适合自己数据的时候,是用了undo log的,但是undo log不能一直留着啊,那么什么时候就不用了呢?这里也取MySQL实战45讲里评论举的一个例子:
事务A开启事务,获取read-view-A,执行查询row1 column1,不提交
事务B开启事务,修改row1记录,假设由column1 a -> b,此时记录undo 日志,提交;
事务C开启事务,修改row1记录,假设由column1 b -> c,此时记录undo 日志,提交;
…
此时事务A读取记录怎么读呢?既然维护了read-view,那么久还不提交,那么在读column1的时候,只能是逐级找undo log了,undo log 也没办法,只能给他留着这些玩意。还是MySQL实战45讲里提到的一个例子,20G的数据,回滚段能到200G。
所以,何时可以删除呢?就是事务A提交了,read-view-A不用再去费劲用这些维护的undo log了
参考
https://dev.mysql.com/doc/refman/5.7/en/innodb-multi-versioning.html
https://dev.mysql.com/doc/dev/mysql-server/9.0.1/classReadView.html#details
https://dev.mysql.com/doc/dev/mysql-server/9.0.1/read0types_8h_source.html
https://github.com/twitter-forks/mysql/blob/master/storage/innobase/include/read0read.h
https://time.geekbang.org/column/article/70562