第 3 章:看清 RR 与 RC 的快照起点
同一个 SELECT 在事务里执行两次,是否一定看见相同结果,取决于隔离级别和读取类型。MySQL 的 REPEATABLE READ 与 READ COMMITTED 都支持一致性非锁定读,但建立快照的时间不同。
3.1 BEGIN 不等于已经固定了普通快照
在本书的 RR 实验中,连接 A 开始事务,尚未做普通一致性读;连接 B 将库存改为 2 并提交。随后 A 第一次 SELECT 读到 2。B 再改为 3 并提交,A 的第二次普通 SELECT 仍读到 2。
A: BEGIN
B: UPDATE stock=2; COMMIT
A: 普通 SELECT -> 2,建立本事务一致性读快照
B: UPDATE stock=3; COMMIT
A: 普通 SELECT -> 2
A: COMMIT
A: 新事务普通 SELECT -> 3
这说明本例 RR 的快照由第一次一致性读建立,不能笼统地把所有 BEGIN 时刻都当作读视图起点。显式使用一致性快照语法属于另一种需要说明的选择。
3.2 RC 每次一致性读使用新的快照
在 RC 实验里,A 第一次读到 1,B 更新为 2 并提交,A 同一事务的下一次普通 SELECT 就读到 2。对于报表和业务决策,读者需要知道多条查询是否希望基于同一个历史视图。
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;修改隔离级别不会自动替你保护库存不变量。RC 并不等于“可以不用锁”,RR 也不等于“所有语句永远只看到旧数据”。下一章会看到锁定读和更新对当前记录的处理。
3.3 自己的修改仍然可见
一致性读一般不看快照建立后的其他事务提交,却能看见自己之前语句所做的修改。这种组合可能呈现出一个从未在其他事务眼里整体存在过的视图。官方手册特别说明了这项边界。
因此,不能把事务内连续几次 SELECT 简单理解为对数据库做了一张绝对静止的照片。需要区分哪些行来自快照,哪些是自己的变化,哪些语句采用了锁定读取。
3.4 隔离级别需要连同访问模式一起讨论
本书实际实验重点是 RC 与 RR。READ UNCOMMITTED 和 SERIALIZABLE 可作为参照,但不能据此声称已完整压测四种隔离级别。不同级别影响读与锁策略,也会与索引、范围和语句形式共同作用。
选择级别时先写业务要求,再验证具体 SQL。面对并发错误,不应先把隔离级别改到最高,期望它消除所有程序缺陷;也不应为了减少等待直接切成 RC,却不重测业务约束。
练习。 在 RR 下先 BEGIN、等待三秒、再做第一次普通 SELECT。另一个事务在这三秒内已经提交。仅凭 BEGIN 更早,能否判断这次 SELECT 一定读不到新值?请画出快照真正建立的时刻。