MySQL系列(二)--Innodb幻读问题解决
「可重复读」隔离级别解决幻读了吗?如何解决的?
对于一张表
id | name | score |
---|---|---|
1 | a | 100 |
2 | b | 200 |
3 | c | 300 |
1. 快照读
在可重复读隔离级别下,有两个事务对上面表的执行顺序如下:
事务A | 事务B |
---|---|
begin; select name from t_stu where id > 2; 查询结果:c |
|
begin; insert into t_stu value(4, “d”, 100); commit; |
|
begin; select name from t_stu where id > 2; 查询结果:c |
|
commit; |
从查询结果可以看到,即使事务 B 中途插入了一条记录,事务 A 前后两次查询的结果集都是一样的,并没有出现所谓的幻读现象。
为什么?
因为在可重复读隔离级别下,普通的查询是快照读,是不会看到别的事务插入的数据的。
可重复读隔离级是由 MVCC(多版本并发控制)实现的,实现的方式是启动事务后,在执行第一个查询语句后,会创建一个视图,然后后续的查询语句都用这个视图,「快照读」读的就是这个视图的数据,视图你可以理解为版本数据,这样就使得每次查询的数据都是一样的。
2. 当前读
MySQL 里除了普通查询是快照度,其他都是当前读,比如update、insert、delete,这些语句执行前都会查询最新版本的数据,然后再做进一步的操作。
这很好理解,假设你要 update 一个记录,另一个事务已经 delete 这条记录并且提交事务了,这样不是会产生冲突吗,所以 update 的时候肯定要知道最新的数据。
select ... for update
这种查询语句是当前读,每次执行的时候都是读取最新的数据。
假如select ... for update
这种当前读,没有对数据进行加锁操作(实际会加锁)
事务A | 事务B |
---|---|
begin; select name from t_stu where id > 2 for update; 查询结果:c |
|
begin; insert into t_stu value(4, “d”, 100); commit; |
|
begin; select name from t_stu where id > 2 for update; 查询结果:c d |
|
commit; |
这时候,事务 B 插入的记录,就会被事务 A 的第二条查询语句查询到(因为是当前读),这样就会出现前后两次查询的结果集合不一样,这就出现了幻读。
所以,Innodb 引擎为了解决「可重复读」隔离级别使用「当前读」而造成的幻读问题,就引出了 next-key 锁,就是记录锁和间隙锁的组合。
- 记录锁,锁的是记录本身;
- 间隙锁,锁的就是两个值之间的空隙,以防止其他事务在这个空隙间插入新的数据,从而避免幻读现象。
需要注意的是,next-key lock 锁的是索引,而不是数据本身,所以如果 update 语句的 where 条件没有用到索引列,那么就会全表扫描,在一行行扫描的过程中,不仅给行加上了行锁,还给行两边的空隙也加上了间隙锁,相当于锁住整个表,然后直到事务结束才会释放锁。
关于next-key锁可以看我的另一篇文章。
- 原文作者:devhg
- 原文链接:https://ihui.ink/post/mysql/02-%E8%A7%A3%E5%86%B3%E5%B9%BB%E8%AF%BB%E9%97%AE%E9%A2%98/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。