在同一个事务里对同一行连续读三次,前两次用普通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: balance = 1000.00 (B执行 UPDATE ... = 500 并提交) 第二次普通SELECT(同一事务内): balance = 1000.00 ← 真实没变,还是快照里的旧值 SELECT ... FOR UPDATE(当前读): balance = 500.00 ← 真实立刻读到了B刚提交的新值
同一个事务、同一行数据,读了三次,前两次真实完全一样(1000.00)——这就是一致性读:事务开始时建立的快照,之后每一次普通SELECT都基于这份快照,不管别的事务提交了什么修改,看到的真实都是快照那一刻的数据。第三次换成FOR UPDATE,这是当前读——它真实不看快照,直接读取当前最新的已提交版本,并且给这一行加锁。
普通SELECT是一致性读(consistent read),真实基于事务开始时的快照,实现了可重复读;带FOR UPDATE/LOCK IN SHARE MODE的SELECT,以及UPDATE/DELETE本身,都是当前读(current read),真实必须读最新已提交的数据再加锁——否则没法保证锁住的是"真实存在的最新数据"。这也是为什么"可重复读"这个隔离级别的名字容易让人误解:它只保证普通SELECT可重复读到同样的结果,一旦在同一个事务里用FOR UPDATE读同一行,读到的值可能和前面的普通SELECT真实不一样。
每一步都是本文上面用两个真实并发会话跑出来的原始结果。
SELECT(一致性读) vs SELECT ... FOR UPDATE(当前读)