bigywater.tech
MySQL / InnoDB · 50

当前读与一致性读

在同一个事务里对同一行连续读三次,前两次用普通SELECT,第三次换成FOR UPDATE,看一致性读和当前读的真实分界线在哪。

1000.00→1000.00
同一事务内两次普通SELECT,真实都停在快照上
1000.00→500.00
换成FOR UPDATE后,真实立刻读到最新值

做的事:同一个事务里,普通SELECT和SELECT...FOR UPDATE分别读三次

accounts表的id=10这一行,初始余额1000。

session A(REPEATABLE READ)START TRANSACTION; SELECT balance FROM accounts WHERE id=10; -- 第一次,普通读 SELECT SLEEP(3); SELECT balance FROM accounts WHERE id=10; -- 第二次,普通读(B已改完提交) SELECT balance FROM accounts WHERE id=10 FOR UPDATE; -- 第三次,当前读 COMMIT; -- session B(在A的SLEEP期间执行) UPDATE accounts SET balance = 500 WHERE id=10;

验证:两次普通SELECT真实都停留在快照上,FOR UPDATE真实立刻跳到最新值

实测 · 本机两个并发会话的真实输出
第一次普通SELECT:               balance = 1000.00
(B执行 UPDATE ... = 500 并提交)
第二次普通SELECT(同一事务内):   balance = 1000.00   ← 真实没变,还是快照里的旧值
SELECT ... FOR UPDATE(当前读): balance = 500.00     ← 真实立刻读到了B刚提交的新值

同一个事务、同一行数据,读了三次,前两次真实完全一样(1000.00)——这就是一致性读:事务开始时建立的快照,之后每一次普通SELECT都基于这份快照,不管别的事务提交了什么修改,看到的真实都是快照那一刻的数据。第三次换成FOR UPDATE,这是当前读——它真实不看快照,直接读取当前最新的已提交版本,并且给这一行加锁。

结论:SQL语句本身决定了读的是快照还是当前值

普通SELECT是一致性读(consistent read),真实基于事务开始时的快照,实现了可重复读;带FOR UPDATE/LOCK IN SHARE MODE的SELECT,以及UPDATE/DELETE本身,都是当前读(current read),真实必须读最新已提交的数据再加锁——否则没法保证锁住的是"真实存在的最新数据"。这也是为什么"可重复读"这个隔离级别的名字容易让人误解:它只保证普通SELECT可重复读到同样的结果,一旦在同一个事务里用FOR UPDATE读同一行,读到的值可能和前面的普通SELECT真实不一样。

分步演示:同一事务里,一致性读和当前读的真实分歧点

每一步都是本文上面用两个真实并发会话跑出来的原始结果。

同一个事务,同一行,三次读出两种真实结果
这一步新验证的读取

    本文用到的工具

    • 本机 MySQL 26.7.0(InnoDB),两个真实并发会话,REPEATABLE READ隔离级别
    • 普通SELECT(一致性读) vs SELECT ... FOR UPDATE(当前读)
    京ICP备2026067502号-1京公网安备11010502063487号