【MySQL 理论】脏读、不可重复读、幻读
1. 脏读(dirty read)
脏读是指事务读取到其他事务未提交的数据
例如:有事务 A、B 和一条记录:id 为 1,name 为张三
B 首先进行更新操作,将 name 的值由张三改为张老三,但还未提交事务
begin;
update stu set name = '张老三' where id = 1然后 A 进行查询操作,查询姓名为张老三
select name from stu where id = 1然后 B 由于某种原因被回滚,name 自然从张老三恢复到了张三
A 就会出现一个问题,读取的是张老三,但数据库中实际存储的是张三,我到底是用哪一个呢?
这就是脏读,事务 A 读取到事务 B 未提交的数据
脏读是我们在整个数据库操作中最普遍的一个现象,但是在日常开发中我们几乎不会遇到脏读,原因后面说
看图更容易理解
2. 不可重复读(non-repeatable read)
不可重复读是指在同一次事务中前后查询不一致的问题
例如:
A 先查询了一条记录,name 为张三
select name from stu where id = 1然后 B 执行了更新,并且提交了
begin;
update stu set name = '张老三' where id = 1;
commitA 再次按相同条件查询该记录,name 为张老三
select name from stu where id = 1A 就会出现一个问题,同样的查询语句,两次的执行结果却不一致(不重复)
第二次查询得到的 name 应该还是预期的张三,因为很明确,A 并没有对 name 进行修改,但是在并发环境下,假如两条 select 语句间有另外一个事务对 name 执行了 update 操作并提交了,把张三的名字改为了张老三,A 再次执行同样查询导致 name 由张三变为张老三
A 就有意见了,我明明没有对 name 进行修改,可是为什么后来变成张老三了?谁改我数据了?
这就是不可重复读:同一次事务中前后查询不一致
它会让我们的程序运行变得不可预期、不可控
看图更容易理解

3. 幻读(phantom read)
幻读是一次事务中前后数据量发生变化,用户产生不可预料的问题
分为并发删除和并发插入的情况
1 并发插入
A 先执行查询,假设当前只有一条数据,查询结果:id=1,name=张三
select * from stuB 插入了一条数据(已设置主键自增,省略 id 字段),name 为李四
insert into stu(name) values('李四')当 A 再次按相同条件查询
select * from stu结果多了一条李四的记录(id=2,name=李四),刚才明明只有一条记录,怎么多出来一条?好像出现了幻觉
2 并发删除
将上面的插入操作换成删除操作,B 删除了张三的记录
delete from stu where id = 1结果 A 会发现张三的记录神秘的消失了,明明刚才还有的,怎么又没了,再次出现了幻觉
这就是幻读,看图更容易理解

4. 不可重复读和幻读的比较
1 幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。
2 不可重复读的重点是修改,幻读的重点在于插入或者删除 3 但如果从控制的角度来看, 两者的区别就比较大 对于前者, 只需要锁住满足条件的记录
对于后者, 要锁住满足条件及其相近的记录
5. 总结
脏读是指事务读取到其他事务正在处理的未提交数据
不可重复读指在并发更新时,另一个事务前后执行相同条件的查询得到的数据不一致
幻读指并发插入、删除时,另一个事务前后执行相同条件的查询得到的数据不一致
6. 解决方法
如何解决以上问题,事务的隔离级别就派上用场了
禁止写时读,避免了“脏读”,对应隔离级别 read committed。
禁止读时写,避免了“不可重复读”,对应隔离级别 repeatable read。
而为了避免“幻读”,干脆把整个表给锁住了,只能是 serialize 了。
隔离级别越高,并行度越低,付出的代价越大。
MySQL 默认事务隔离级别为:可重复读(repeatable-read),因此当我们使用 MySQL 进行实际开发时一般不会发生“脏读”和“不可重复读”。现在可能遇到的问题就是“幻读”,不过 MySQL 通过多版本并发控制(MVCC)机制解决了该问题。