背景
在 MySQL 的 InnoDB 存储引擎中,REPEATABLE READ
是默认的事务隔离级别。在这个隔离级别下,每个事务在开始时会创建一个 Read View
,这个 Read View
记录了事务开始时所有活跃事务的 ID。事务在执行过程中会一直使用这个 Read View
,即使其他事务提交了更改。
示例
假设我们有两个会话,都关闭了自动提交,并且使用默认的 REPEATABLE READ
隔离级别。
初始化数据
CREATE TABLE t_user (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50),
age INT
);
INSERT INTO t_user (name, age) VALUES ('Alice', 25), ('Bob', 30);
会话 1
-- 关闭自动提交
SET autocommit = 0;
-- 开始事务
START TRANSACTION;
-- 查询初始数据
SELECT * FROM t_user;
会话 2
-- 关闭自动提交
SET autocommit = 0;
-- 开始事务
START TRANSACTION;
-- 更新数据
UPDATE t_user SET age = 35 WHERE name = 'Bob';
-- 查询更新后的数据
SELECT * FROM t_user;
会话 2 提交事务
COMMIT;
会话 1 再次查询
-- 再次查询数据
SELECT * FROM t_user;
结果分析
-
会话 1 第一次查询:
- 查询结果将是初始数据:
+----+-------+-----+ | id | name | age | +----+-------+-----+ | 1 | Alice | 25 | | 2 | Bob | 30 | +----+-------+-----+
- 查询结果将是初始数据:
-
会话 2 更新数据并查询:
- 查询结果将是更新后的数据:
+----+-------+-----+ | id | name | age | +----+-------+-----+ | 1 | Alice | 25 | | 2 | Bob | 35 | +----+-------+-----+
- 查询结果将是更新后的数据:
-
会话 2 提交事务:
- 会话 2 提交事务后,更新的数据已经持久化到数据库中。
-
会话 1 再次查询:
- 查询结果仍然是初始数据,因为会话 1 的事务在开始时创建了一个
Read View
,这个Read View
记录了事务开始时所有活跃事务的 ID。 - 由于会话 1 的事务在开始时记录了会话 2 的事务 ID(假设为 101),即使会话 2 的事务已经提交,会话 1 的事务仍然只会看到事务开始时的数据版本。
+----+-------+-----+ | id | name | age | +----+-------+-----+ | 1 | Alice | 25 | | 2 | Bob | 30 | +----+-------+-----+
- 查询结果仍然是初始数据,因为会话 1 的事务在开始时创建了一个
详细解释
-
Read View
的创建:- 当会话 1 开始事务时,会创建一个
Read View
,记录了当前所有活跃事务的 ID。 - 假设会话 1 的事务 ID 是 100,会话 2 的事务 ID 是 101。
- 当会话 1 开始事务时,会创建一个
-
Read View
的作用:- 会话 1 的
Read View
记录了事务 ID 101 是活跃的。 - 当会话 1 再次查询数据时,会检查每一行的
DB_TRX_ID
:- 如果
DB_TRX_ID
小于m_up_limit_id
(即 100),则该版本是已提交的,可以被看到。 - 如果
DB_TRX_ID
在m_ids
列表中(即 101),则该版本是未提交的,不能被看到。
- 如果
- 即使会话 2 的事务已经提交,会话 1 的事务仍然会使用最初的
Read View
,因此只能看到事务开始时的数据版本。
- 会话 1 的
-
事务提交的影响:
- 会话 2 提交事务后,更新的数据已经持久化到数据库中,但会话 1 的事务仍然会使用最初的
Read View
,因此不会看到这些更改。
- 会话 2 提交事务后,更新的数据已经持久化到数据库中,但会话 1 的事务仍然会使用最初的
结论
- 未提交的数据:在一个会话中未提交的事务所做的更改,其他会话是无法看到的。
- 已提交的数据:即使其他会话提交了更改,当前事务在
REPEATABLE READ
隔离级别下仍然会使用最初的Read View
,因此只能看到事务开始时的数据版本。