作者简介
无为,多年 MySQL DBA 工作经验,现就职于某知名互联网公司,对 MySQL、 Redis、PostgrepSQL 等主流数据库有一定了解,拥有丰富的一线运维经验。
幻读(Phantom Read),简单的说,指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。
很多书籍或者文章都说 MySQL 通过 next-key lock 是解决了幻读的,但真的是那样吗?我们不妨来验证下(本节实验均在 RR 隔离级别下进行的)。
1 准备环境
代码语言:javascript复制mysql> create table ord (id int,b varchar(10))ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Query OK, 0 rows affected (0.04 sec)
mysql> insert into ord select 1,'a';
Query OK, 1 row affected (0.02 sec)
mysql> insert into ord select 2,'b';
Query OK, 1 row affected (0.01 sec)
mysql> insert into ord select 3,'c';
Query OK, 1 row affected (0.01 sec)
2 场景一
复习一下 next-key lock 的含义: 当 sql 语句按照范围查询非唯一索引列,并且有数据命中的时候会给索引加锁,锁住命中索引项的前一个索引到命中索引项的后一个索引之间的一个左开右闭区间
结论:
select * from user 是快照读,是 MVCC 避免了幻读。
select * from user lock in share mode,当前读,触发了幻读。-- select .... for update; 同理
换句话说:MySQL innodb 在 RR 隔离下一样会出现幻读,next-key lock 和 MVCC 只解决了部分幻读的场景。
2 场景二
结论:
由于 update 语句采用的是当前读,会对 A 线程中新增的行数据加锁、修改数据以及事物 ID,导致 B 线程出现幻读。
3 总结
由于 MySQL 对于普通读(简单 select 语句)是通过 MVCC 获取快照数据,而 select ... for update、select ... lock in share mode、update 、delete 等操作采用的是当前读,所以会造成在某些场景出现幻读的情况。
因此为了保证操作安全,可以采用事物开始时手动加锁来解决(select ... for update、select ... lock in share mode 等)。
关于普通读和当前读的概念、原理可查阅当前读和快照读的区别。