背景
这个事情要回溯到曾经背八股文的时候了, 想必大家在背八股文的时候对于事务隔离级别都已经背得滚瓜烂熟了, 一般在说隔离级别的时候, 都顺带会提到 MySQL 的 innodb 的 RR 隔离级别, 由于他与众不同的实现方式, 通常会有下面的一些描述:
在我的脑海里面一直就记着, MySQL 的 Innodb 在 RR 隔离级别下就能避免幻读(曾经面试的时候也这样回答过), 但是直到有一天群里的同学抛出了一个问题,
我的第一反应也是
怎么定义幻读?
其实对于这个争论, 很多点在于什么才叫做幻读? 先来一个幻读的通俗的定义, 对于相同的区间查询, 插入和删除操作使得对相同的区间查询操作返回不同的结果.
在 Innodb 的 RR 隔离级别下, 比如我们对一个表进行 (id>1 and i < 100) 的删除操作, 另外一个事务这个时候插入一条 id=50 的数据, 如果插入成功的话就会导致我们第一个事务出现幻觉, 所以在 inndodb 中使用了 next-key lock 算法, 也就是加了间隙锁, 从而阻止插入意向锁.
接下来我们再看一下 MySQL 官方定义的幻读:
翻译过来其实就是: 当同一个查询在不同的时间产生不同的集合时, 就会发生所谓的幻读问题. 例如, 如果一个 SELECT 执行了两次, 但是第二次返回了第一次没有返回的行, 那么该行就是一个 "幻像" 行.
这个定义和我们开始那个定义有什么区别吗? 看起来区别不大, 但是细细的品味第一个定义限制了插入和删除. 在 MySQL 的官方定义下, 用了两次查询, 并没有定义另外一个事务做了什么, 以及两次查询之间发生了什么, 所以出现了这样的一个情况:
上面有两个事务, 事务 B 发生了幻读的现象, 为什么说这里是幻读的现象呢? 因为按照 MySQL 的定义两次查询返回不同集合, 事务 B 的确是发生了幻读现象.
为什么会出现这个情况呢? 这个主要还是因为在 innodb 下所有的读都是快照读, 如果我们在事务中对这个数据加锁, 那么就变成了当前读, 所以就能读取到事务 A 写的数据了. 这种情况在一些文献中也被叫做: write skew style phantom.
RR 级别如何解决幻读?
其实我们细细分析, 我们上面那个情况是怎么解决的幻读, 是依靠 next-key lock, 而我们第二个案例虽然在事务中但是却没有使用 next-key lock, 如果我们真的对幻读有很多要求的话, 那么我们在查询的时候直接加上 select ... for update 加上锁, 这样可以直接让我们走当前读, 从而避免幻读的出现.
最后
这篇文章营养价值不高, 主要是用来纠正大家一些观念, 有时候八股文盲目去背没有细细思考, 可能就会导致认知上的错误. 最后总结一下, 在 RR 隔离级别下只要不出现快照读和当前读的切换, 其实就能保证不会出现幻读.
来源: http://database.51cto.com/art/202108/679231.htm