Skip to content

Latest commit

 

History

History
79 lines (63 loc) · 4.94 KB

File metadata and controls

79 lines (63 loc) · 4.94 KB

关于MySQL在RR级别中是否解决了幻读的问题, 一直以来都有两种说法,其中的争议点其实是:幻读的定义

  1. 幻读指在同一事务下,连续执行两次同样的SQL语句可能导致不同的结果。引用自《MySQL技术内幕:InnoDB存储引擎》
    类似定义的有幻读 WikipediaMySQL官方文档
  2. 另一种看法则认为, 如果执行不同的SQL语句导致了不同的结果, 也属于幻读 如此文InnoDB Repeatable Read隔离级别之大不同

重复执行两次相同的SQL

分为两种情况: 两次快照读和两次当前读

模拟数据:

create table student
(
    id   int unsigned,
    name varchar(100),
    primary key (id)
);
insert into student values (1, '小明');

两次快照读

T1事务 T2事务(干扰事务)
begin; begin;
select * from student where id > 0;
insert into student (id, name) values (2, '新记录');
select * from student where id > 0;
commit;
  1. T1事务两次执行相同的快照读SQL, 得到的结果是一致的.
    原因是: 在MVCC中, 第一次select时, 生成了事务ID, 同时生成了T1事务能够读取到的数据的快照,
    而T2事务在T1事务生成快照后执行insert, 则插入的数据不属于快照内容
    所以当第二次重复select时, 查询到的事务快照的结果是一致的
  2. 因为T2事务insert后没有立即commit, 所以RC级别也是相同的结果, 原理也是一样

两次当前读

T1事务 T2事务(干扰事务)
begin; begin;
select * from student where id > 0 for update;
insert into student values (2, '新记录');
(阻塞中, 等待执行)
select * from student where id > 0 for update;

虽然ANSI SQL标准中定义的RR级别是存在幻读可能性的,
但是特殊地, 在MySQL中, 由于T2事务的insert并未实际执行, 所以T1事务的两次当前读必然是相同结果
原因在于MySQL有Next-key锁,

  1. 当T1事务第一次当前读时, 不仅将当前存在的记录(id=1)施加行锁,
  2. 同时针对当前尚不存在的记录(如id=2)之间的间隙, 施加间隙锁
    (因为记录当前不存在, 所以无法施加行锁)
    通过间隙锁, 使得T2事务阻塞, 无法执行在id>0的间隙中插入数据

总结

这样, MySQL在重复执行相同的SQL时(两次快照读和两次当前读), 结果都一致, 所以认为, 在MySQL的RR级别下, 通过MVCC+Next-key锁, 解决了幻读问题

先快照读, 再当前读

T1事务 T2事务(干扰事务)
begin; begin;
select * from student where id > 0;
insert into student values(2, '新记录');
commit;
(这样T1事务才能执行当前读)
select * from student where id > 0 for update;

此时, 两次select(一次快照读, 一次当前读)结果不一致!
第二次select不是读取快照, 而是读取数据库当前状态数据, 所以会查询到T2事务刚插入的数据
因为在逻辑上这确实也是一种"不可重复读", 所以部分人认为这也是幻读, 且不能被RR级别解决, 只能Serializable级别中解决

其他参考