Skip to content

读已提交隔离级别下如何读取数据

在读已提交(Read Committed)隔离级别下,读取数据的方式完全依赖于MVCC(多版本并发控制)机制,其核心的实现细节在于ReadView(读视图)的创建时机。

简单来说,它的读取策略可以概括为:在事务中,每一次执行SELECT语句,都会生成一个新的、即时的“数据快照”。

核心机制:为每个SELECT语句创建ReadView

在RC隔离级别下,当一个事务执行一条SELECT语句时,InnoDB会为这条语句创建一个全新的ReadView。

这个ReadView可以被理解为在SELECT语句执行的那一瞬间,整个数据库的一个“快照”。这个快照包含了以下关键信息: * 当前所有活跃(未提交)的事务ID列表。 * 活跃事务中的最小ID。 * 下一个将要分配的事务ID。

这个ReadView的作用就是作为一个“可见性标尺”,用来判断数据行中的哪个版本对当前的SELECT操作是可见的。

一个具体的读取流程示例

为了更好地理解,我们来看一个经典的“不可重复读”场景是如何在RC级别下发生的:

假设我们有一个账户表accounts,其中一行数据为 (id=1, balance=100)

  1. 事务A开启。 START TRANSACTION;

  2. 事务A执行第一次SELECT (T1时刻)。 SELECT balance FROM accounts WHERE id = 1; 此时,InnoDB为这条SELECT创建了一个ReadView_1。 ReadView_1发现当前balance=100的这个数据版本,是在它创建之前就已经提交的,因此是可见的。 事务A查询到的结果是:100

  3. 与此同时,事务B开启,修改数据并提交 (T2时刻)。 START TRANSACTION; UPDATE accounts SET balance = 150 WHERE id = 1; COMMIT; 事务B的UPDATE操作会生成一个新的数据版本,balance=150。旧版本balance=100会被放入Undo Log。

  4. 事务A在内部继续执行,并进行第二次SELECT (T3时刻)。 SELECT balance FROM accounts WHERE id = 1; 这是RC级别最关键的一步:因为这是一条新的SELECT语句,InnoDB会重新创建一个全新的ReadView_2。 在创建ReadView_2的这一刻,事务B已经提交了。所以,在ReadView_2的“活跃事务列表”中,已经不包含事务B的ID了。

  5. 可见性判断: 事务A使用ReadView_2去读取id=1的这行数据。它首先看到的是最新版本,即balance=150,这个版本是由事务B修改的。 它会用ReadView_2来判断这个版本的可见性。它发现,修改这个版本的事务B,其ID不在ReadView_2的活跃事务列表中。 根据MVCC规则,这意味着事务B是一个已经提交了的事务,所以它的修改对当前这个SELECT操作是可见的。 因此,事务A第二次查询到的结果是:150

通过上面的流程,我们可以清晰地看到: * RC级别如何解决脏读:在任何一个ReadView中,都只包含已提交的事务信息。因此,一个事务永远不会读到另一个事务尚未提交的数据。 * RC级别为何会产生不可重复读:在一个事务内部,由于每次SELECT都会生成新的ReadView,如果两次SELECT之间有其他事务提交了修改,那么后一个ReadView就能“看到”这个新提交的修改,导致两次读取的结果不一致。

可以做一个比喻: * 读已提交(RC)的读取方式,就像是你每次想看一个东西时,都重新拍一张照片来看。两张照片之间,东西可能已经变了。 * 而可重复读(RR)的读取方式,就像是你只在第一次看的时候拍一张照片,之后无论再看多少次,都只看这张旧照片。

因此,读已提交隔离级别通过“为每个SELECT生成新快照”的方式,在保证不读到脏数据的前提下,实现了较高的并发性能,是许多数据库系统的默认选择。