1. 为什么说间隙锁解决了幻读?不是MVCC解决的吗?
首先需要理解幻读和不可重复读的概念和区别。
不可重复读:在一个事务中,两次读取同一数据,因为其他事务的更改导致读出的结果不一致。
例如:事务A 读取 select name, age from t where id = 1 -> 结果为 (name,age) = ("张三",18). 再次读取id = 1 的数据时,因为其他事务的更改(事务B update table t set age = 19 where id = 1),读到的结果变为 ("张三",19).
幻读:在一个事务中,两次读取数据,因为其他事务的插入导致读出的结果发生变化.
例如:table t中有两条数据(id,name,age) = {(1, "张三", 19),(4, "李四", 24)}。
事务A中有两条sql:
- select * from t where id = 3;
- insert into table t(id, name, age) values (3, "王五", 30); 在查询了第一条数据之后,发现id=3没有数据,就执行插入操作。 但是因为事务B在事务A插入前,执行了 insert into table t(id, name, age) values (3, "曹操", 50); 所以在事务A执行insert语句时,当前读发现id=3的数据存在,就感觉像出现幻觉了一样。 这就是幻读。
MVCC,多版本并发控制。在可重复读隔离级别下,事务开启后,读取第一条数据前会生成当前数据的一个快照,这个快照的版本号与当前事务挂钩。
读取数据之后会比较数据的版本号与当前事务创建的快照版本号,如果高于当前版本号,就说明是其他事务在快照创建后进行修改的,就会通过undo log 计算出更改之前的数据,直到找到的数据的版本号等于或者小于当前版本号。
MVCC多版本控制能够解决不可重复读的问题。但是无法解决幻读的问题。因为数据更新是当前读,当前读必须读取最新的数据,这样才能保证数据一致性。
解决幻读的方法是间隙锁。间隙锁就是锁住数据之间的间隙,在锁没有释放之前,别的事务是无法插入这些间隙的。这样就能保证不出现幻读。上面案例中,在MySQL innoDB 可重复读隔离级别,将事务A的select语句改为 select * from t where id = 3 for update; 其他事务就不能够执行id 在(1,4)的更新插入操作。