RR解决了什么?
RR解决了脏读的问题(保证了在同一个事务下,多次读取同样的数据的结果是一致的),最大功臣就是MVCC机制。但是这也导致RR级别出现的幻读问题,在特定情况下,还是无法彻底解决,本文指在探讨幻读产生的原因,方便之后在开发过程中,避免可能导致幻读情况发生的操作。
其他事务隔离级别暂不探究,探究使用较多的RR。
MySQL 使用了多版本并发控制(MVCC)来处理并发事务。MVCC 允许事务在读取数据时不被其他事务的更新所干扰,但在插入或更新数据时,会进行行级锁的加锁操作,以保证数据的一致性。然而,由于行级锁只能锁定已存在的数据行,无法锁定不存在的数据行。
幻读:假设A事务在读取D范围的记录时,B事务又在D范围内插入新的记录,那么A事务再次读取D范围的记录时,就会出现"幻行"。这是其中一种情况。
MVCC只在RR(可重复读),RC(读已提交,不可重复读,一个事务从开启到提交,做的修改操作对其他事务都是不可见的)下工作,RU(读未提交)和串行化下不工作。RU总是读取最新数据,串行化对读取行都加锁。
很多数据库都有实现自己的MVCC,实现机制各有不同;Mysql的MVCC,是通过保存数据在某个时间点的快照来实现的,保证不管事务执行多久,看到的数据都是一致的,类似与时间链,事务开始时间不同,看到的数据也可能不一样。
InnoDB的MVCC,是通过在每行记录后面保存两个隐藏列实现。一个列是行创建时间,一个是行删除时间,
当然这里的时间指的是系统指定的版本号。每开启一个新事务,系统版本号都会递增,事务开始时刻的系统版本号作为事务的版本号,用来和查询的行记录的版本号进行比较。使大多数读操作都可以不用加锁
如何比较:
SELECT:
条件1:查找小于等于当前事务版本号的数据行(早于当前事务创建时间),这样可以保证读取到的行,是已经存在或者事务自己插入或者修改过的。(这里的插入,修改往后面看)
条件2:行的删除版本要么没有定义,要么大于当前事务版本号,这样可以确保读取到的行,在事务开始前没有被删除。
INSERT:
为新插入的行,保存当前的系统版本号作为行版本号;这里就对应上面的SELECT了。
DELETE:
删除的行,保存当前的系统版本号作为行删除版本。
UPDATE:
更新时,保存当前系统版本号作为行版本号,行删除版本。
- 插入新数据:一个事务在可重复读隔离级别下执行了一个查询,返回一组数据。然后另一个事务插入了符合该查询条件的新数据,并提交了事务。当第一个事务再次执行相同的查询时,会发现多了一条之前不存在的数据,从而产生幻读。
- 删除已有数据:一个事务在可重复读隔离级别下执行了一个查询,返回一组数据。然后另一个事务删除了其中的一些数据,并提交了事务。当第一个事务再次执行相同的查询时,会发现之前查询到的数据中有一部分已经被删除了,从而产生幻读。
- 修改已有数据:一个事务在可重复读隔离级别下执行了一个查询,返回一组数据。然后另一个事务修改了其中的一些数据,并提交了事务。当第一个事务再次执行相同的查询时,会发现之前查询到的数据发生了变化,从而产生幻读。
间隙锁(Gap Lock)是MySQL中的一种行级锁,用于解决幻读问题。它通常与可重复读或更高的隔离级别一起使用。
间隙锁的作用是锁定一个范围的键值之间的间隙(即不存在的键值范围),防止其他事务在该范围内插入新的数据,从而避免了幻读问题的发生。
事务A:
START TRANSACTION; SELECT * FROM table WHERE column > 10 AND column < 20 FOR UPDATE;
- 1
- 2
事务B:
START TRANSACTION; INSERT INTO table (column) VALUES (15); COMMIT;
- 1
- 2
- 3
在事务A执行SELECT语句时,它会获取一个范围锁,锁定了 column > 10 and column < 20 的间隙。因此,事务B在该范围内插入数据时会被阻塞,直到事务A释放间隙锁。