可重复读为什么能避免一部分幻读,但是没能完全避免幻读

事物的隔离级别已经介绍过了,接下来我们谈谈细节部分。
MySQL innoDB引擎的默认隔离级别——可重复读
虽然可重复读不能完全避免幻读,但其实已经避免了很大一部分了。具体怎么做的呢,主要有以下两个操作:

  • 针对快照读,是通过MVCC(多版本并发控制)方式解决了幻读。意思就是说事务开始后,执行第一个查询语句时,会创建一个 Read View(快照),后续查询的结果都是根据根据这个快照得出的。这个快照是根据undo log 版本链实现的,后续会说。
  • 针对当前读,是通过next-key lock(记录锁+间隙锁)方式解决了幻读。需要注意的是,快照读仅针对普通的select查询。假设事务想执行update、insert、delete,就得先去看当前最新的数据是什么样的,这就是当前读。否则你想update,但是其他事务delete了并且提交了,那这逻辑就冲突了。所以其实有一种特殊的查询语句select … for update,区别于普通的select查询,这种查询语句是当前读。但这就出现了幻读,如下图:
    可重复读为什么能避免一部分幻读,但是没能完全避免幻读_第1张图片
    所以,为了解决幻读问题,select … for update有一种机制叫做next-key lock(间隙锁+记录锁)可重复读为什么能避免一部分幻读,但是没能完全避免幻读_第2张图片

注意区间是 ( 2 , + ∞ ] (2, +\infty] (2,+],为什么呢,因为next-key lock是间隙锁(gap lock,锁住一个左开右开的开区间,阻止插入)和记录锁(record lock,也称行锁,只锁住一行,阻止删除更新)的合体。

但是为啥说这俩机制没完全解决幻读呢?主要是以下两种情况没法避免
情况一

# 事务 A
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from t_stu where id = 5;
Empty set (0.01 sec)
# 事务 B
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into t_stu values(5, '小美', 18);
Query OK, 1 row affected (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

我们会发现,A事务其实没主动插入id=5的数据,是B事务插入并且提交的,A数据想要执行update就不能用快照,得用当前数据的版本,所以就把B的数据读出来了,然后就给更新了。所以MVCC是不能完全避免幻读的。

# 事务 A
mysql> update t_stu set name = '小林coding' where id = 5;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from t_stu where id = 5;
+----+--------------+------+
| id | name         | age  |
+----+--------------+------+
|  5 | 小林coding   |   18 |
+----+--------------+------+
1 row in set (0.00 sec)

情况二

  • T1 时刻:事务 A 先执行「快照读语句」:select * from t_test where id > 100 得到了 3 条记录;
  • T2 时刻:事务 B 往插入一个 id= 200 的记录并提交;
  • T3 时刻:事务 A 再执行「当前读语句」 select * from
    t_test where id > 100 for update 就会得到 4 条记录,此时也发生了幻读现象。
    这个现象是因为没能早点锁住记录,所以尽量在开启事务之后,马上执行 select … for update 这类当前读的语句,从而避免其他事务插入一条新记录。

你可能感兴趣的:(mysql,数据库,android)