事务隔离-高低水位

事务隔离-高低水位

最近在复习丁奇大大MySQL实战45讲 - 08 | 事务到底是隔离的还是不隔离的?中关于 RC 隔离级别的实现时,以前好像读懂的地方又读不懂了。尤其是对下👇图:数据版本可见性规则 中高低水位『当前事务』的理解。

疑问

在可重复读隔离级别下,事务在启动的时候就『拍了个快照』。

按照可重复读的定义,一个事务启动的时候,能够看到所有已经提交的事务结果。但是之后,这个事务执行期间,其他事务的更新对它不可见。

按照定义来说,当该事务在启动的时候就『拍了个快照』。那么快照发生时,该事务应该是当前最新(latest)的事务,该快照中不应该包含该时候之后的事务了。即『当前事务』不应该在下图中『未提交事务集合』的最右侧么?为什么图片中体现的是『当前事务』右侧还有其他事务?

数据版本可见性规则

InnoDB 为每个事务构造了一个数组,用来保存这个事务启动瞬间,当前正在『活跃』的所有事务 ID。『活跃』指的就是,启动了但还没提交。数组里面事务 ID 的最小值记为低水位,当前系统里面已经创建过的事务 ID 的最大值加 1 记为高水位。这个视图数组和高水位,就组成了当前事务的一致性视图(read-view)

对于当前事务的启动瞬间来说,一个数据版本的 row trx_id,有以下几种可能:

  1. 如果落在绿色部分,表示这个版本是已提交的事务或者是当前事务自己生成的,这个数据是可见的;
  2. 如果落在红色部分,表示这个版本是由将来启动的事务生成的,是肯定不可见的;
  3. 如果落在黄色部分,那就包括两种情况:
    1. 若 row trx_id 在数组中,表示这个版本是由还没提交的事务生成的,不可见;
    2. 若 row trx_id 不在数组中,表示这个版本是已经提交了的事务生成的,可见。

还有上👆面分析中的 3.2 ,感觉前后矛盾。明明黄色部分是『未提交事务集合』,为什么又说『这个版本是已经提交了的事务生成的』??

求索

在 MySql 的官方文档中,发现有这么一段话:

InnoDB can avoid the overhead associated with setting up the transaction ID (TRX_ID field) for transactions that are known to be read-only. A transaction ID is only needed for a transaction that might perform write operations or locking reads such as SELECT ... FOR UPDATE. Eliminating unnecessary transaction IDs reduces the size of internal data structures that are consulted each time a query or data change statement constructs a read view.[^1]

InnoD B可以避免为已知为只读的事务设置 trx_id 带来的开销。只有可能执行写操作或 locking read 的事务才需要(分配)trx_id 。避免分配不必要的 trx_id 可以减少每次查询或数据更改语句构造读视图时所查询的内部数据结构的大小。

我的理解是,,如果事务开始后,执行第一条 [写操作 | locking reads ] sql 时才会分配 trx_id。

另外,丁奇大大原文中还提到的这样几句话:

begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个操作 InnoDB 表的语句,事务才真正启动。如果你想要马上启动一个事务,可以使用 start transaction with consistent snapshot 这个命令。

  1. 第一种启动方式,一致性视图是在执行第一个快照读语句时创建的;

  2. 第二种启动方式,一致性视图是在执行 start transaction with consistent snapshot 时创建的。

关于『第一种启动方式,一致性视图是在执行第一个快照读语句时创建的』,MySql官方文档中是这样描述的:👇

If the transaction isolation level is REPEATABLE READ (the default level), all consistent reads within the same transaction read the snapshot established by the first such read in that transaction…

With READ COMMITTED isolation level, each consistent read within a transaction sets and reads its own fresh snapshot.[^2]

RR 隔离级别下,同一事务中的所有一致读都将读取该事务中第一个此类读(一致性读)所建立的快照。

RC 隔离界别下,事务中的每个一致读都会设置并读取自己的新快照。

我们可以从中得出以下信息:

  1. 事务启动时间 before 一致性视图创建时间;
  2. 事务启动时间 before trx_id 分配时间;
  3. 事务不一定会创建 read view,eg. 事务中只有一个 update 语句;
  4. 对于复杂事务,先执行 写操作 语句,则先分配 trx_id ;先执行 Consistent Reads 则先创建 read view。

解惑

所以,对于之前提到的『图片中体现的是当前事务右侧还有其他事务?』,这里可以做出解答了:

↓ 事务 A 启动;

↓ 事务 B 启动,并执行[写操作 | locking reads ] ,得到 trx_id:B;

↓ 事务 A 执行 consistent read 操作,创建 read view;

↓ 事务 A 执行[写操作 | locking reads ] ,得到 trx_id:A;

trx_id:B < trx_id:A

InnoDB 里面每个事务有一个唯一的事务 ID,叫作 transaction id。它是在事务开始的时候向 InnoDB 的事务系统申请的,是按申请顺序严格递增的[^3]。

对于第二个疑问,有如下场景:

↓ 事务 A 启动,并执行[写操作 | locking reads ] ,得到 trx_id:A;

↓ 事务 B 启动,并执行[写操作 | locking reads ] ,得到 trx_id:B;

↓ 事务 B 提交;

↓ 事务 C 启动,创建一致性视图,并执行[写操作 | locking reads ] ,此时事务 A 为活跃事务;

则 trx_id:B 大于低水位,小于高水位,但不在数组内。


[^1]: Optimizing InnoDB Read-Only Transactions
[^2]: Consistent Nonlocking Reads
[^3]: 没摘自课程原文,没有在官方文档中找到原文,知晓的同学还请告知下。

贺子_DBA时代: mysql XID和trx_id小结