读已提交隔离级别下如何读取数据
在读已提交(Read Committed)隔离级别下,读取数据的方式完全依赖于MVCC(多版本并发控制)机制,其核心的实现细节在于ReadView(读视图)的创建时机。
简单来说,它的读取策略可以概括为:在事务中,每一次执行SELECT语句,都会生成一个新的、即时的“数据快照”。
核心机制:为每个SELECT语句创建ReadView
在RC隔离级别下,当一个事务执行一条SELECT语句时,InnoDB会为这条语句创建一个全新的ReadView。
这个ReadView可以被理解为在SELECT语句执行的那一瞬间,整个数据库的一个“快照”。这个快照包含了以下关键信息:
* 当前所有活跃(未提交)的事务ID列表。
* 活跃事务中的最小ID。
* 下一个将要分配的事务ID。
这个ReadView的作用就是作为一个“可见性标尺”,用来判断数据行中的哪个版本对当前的SELECT操作是可见的。
一个具体的读取流程示例
为了更好地理解,我们来看一个经典的“不可重复读”场景是如何在RC级别下发生的:
假设我们有一个账户表accounts,其中一行数据为 (id=1, balance=100)。
-
事务A开启。
START TRANSACTION; -
事务A执行第一次
SELECT(T1时刻)。SELECT balance FROM accounts WHERE id = 1;此时,InnoDB为这条SELECT创建了一个ReadView_1。 ReadView_1发现当前balance=100的这个数据版本,是在它创建之前就已经提交的,因此是可见的。 事务A查询到的结果是:100。 -
与此同时,事务B开启,修改数据并提交 (T2时刻)。
START TRANSACTION;UPDATE accounts SET balance = 150 WHERE id = 1;COMMIT;事务B的UPDATE操作会生成一个新的数据版本,balance=150。旧版本balance=100会被放入Undo Log。 -
事务A在内部继续执行,并进行第二次
SELECT(T3时刻)。SELECT balance FROM accounts WHERE id = 1;这是RC级别最关键的一步:因为这是一条新的SELECT语句,InnoDB会重新创建一个全新的ReadView_2。 在创建ReadView_2的这一刻,事务B已经提交了。所以,在ReadView_2的“活跃事务列表”中,已经不包含事务B的ID了。 -
可见性判断: 事务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生成新快照”的方式,在保证不读到脏数据的前提下,实现了较高的并发性能,是许多数据库系统的默认选择。