MySQL:什么是脏读、不可重复读和幻读,InnoDB如何解决它们?

一言蔽之,InnoDB用行级锁解决脏读,用MVCC解决不可重复读的问题,使用临键锁解决幻读问题。关于InnoDB加锁的方式,可以参考:

MySQL相关(八)- innodb行级锁深入剖析-阿里云开发者社区 (aliyun.com)

脏读、不可重复读、幻读是什么

脏读

举个例子:

现在有一行数据:
id = 1, value = 10;

事务A修改了id为1的数据,将value改为了20,此时事务B来读取数据,读到value = 20;

但A突然回滚了事务,也就是放弃了修改操作,此时value = 10,但是B读到的数据是value = 20,这就是脏读,也就是指在一个事务处理过程里读取了另一个未提交的事务中的数据

不可重复读

还是这个例子:
id = 1, value = 10;

事务A有两条读取命令,先读取一次,获得value = 10, 然后事务B修改value = 20,提交事务,此时A再读取,就会获取到value = 20;

同一个事务前后两次读取获得的数据不一致,这就是不可重复读。

幻读

现在有三行数据,id为主键,value具备唯一索引

id = 1, value = 10;

id = 3, value = 30;

id = 5,,value = 50;

现在事务A有两条指令:1、查询value等于40的数据  2、如果不存在则插入。

A查询value等于 40 的数据,返回为空,那么事务A紧接着就要新增一条value为40的数据,但这个时候事物B突然插入了一条id = 4, value = 40的数据,现在事物A如果插入value = 40的数据就会报错,违反了唯一性约束。

这种当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,导致一个事务的读取结果不能支撑后续操作实现的,就是幻读。

事务隔离级别及InnoDB实现

查看、设置隔离级别

查看:

select @@transaction_isolation;
设置:
set session transaction isolation level read uncommitted;

未提交读 RU (Read Uncommit)

RU隔离级别不会对数据加锁,所以可能发生脏读、可重复读和幻读。

已提交读 RC(Read Commit)

RC通过添加行级锁的方式保证不会发生脏读,但是一旦select完毕,锁就被释放,另一个事务可以如果乘机修改数据,所以可能发生可重复读和幻读。

可重复读 RR (Repertable Read)

InnoDB实现的RR级别通过行锁保证不会脏读,使用MVCC来管理事务中的数据版本。每个事务看到的是它开始时数据库中数据的快照,而不是当前时刻的数据库状态。这意味着即使其他事务提交了修改,正在进行的事务仍然看到的是一致的数据视图,从而避免了不可重复读的问题。

同时InnoDB还会使用 临键锁 来保证不会发生幻读现象,所以RR可能发生幻读,但是使用InnoDB实现的RR级别不会幻读。

需要注意的是,虽然RR级别通常能够避免不可重复读,但对于幻读的完全解决通常需要Serializable隔离级别,因为Next-Key Locks虽然能大大减少幻读的发生,但在某些特定场景下(如范围查询后插入新记录),还是有可能出现幻读现象。

补充

在使用MVCC时考虑这么一种情况:

A修改了快照a的某一条数据,恰好,B是和A几乎同一时间拿到的快照b,快照a和b的内容完全相等,此时B也修改了b中的同一行数据,双方提交事务时,就会产生并发问题。

针对这种场景,InnoDB通过以下机制来尽量避免并发问题:

  1. 乐观锁和悲观锁策略:InnoDB在事务准备提交时进行冲突检查。如果A事务先修改并提交了数据,当B事务尝试提交时,InnoDB会检测到该行已被修改(通过比较事务开始时的事务ID和当前行的最新事务ID),这时B事务将无法成功提交,除非它能根据事务隔离级别和具体策略进行相应的回滚或重试操作。

  2. 行锁:当A事务开始修改某一行时,它会获取对该行的独占锁(X锁),阻止其他事务同时修改该行。因此,如果B事务稍后也尝试修改同一行,它必须等待A事务完成并释放锁,或者B事务可能直接因锁等待超时而失败,从而避免了并发修改同一数据的问题

串行化 Serializable

将所有的事务强制排序,不会发生任何问题,但是性能极差。

补充:推荐使用的隔离级别

参考:

互联网项目中mysql应该选什么事务隔离级别 - 知乎 (zhihu.com)

先说结论:推荐使用 RC。

在MySQL中,默认的隔离级别是RR,但是RR在未命中索引时会锁表,而且RR使用了间隙锁,这使得出现死锁的概率增大。而且在Oracle、SqlServer中哦都是默认使用RC,MySQL使用RR是因为在RC隔离级别下主从复制会出bug(这是源于mysql 5.0 版本之前binlog日志只支持 STATEMENT格式),所以默认的隔离级别是RR而非RC。

在实际应用场景中更推荐RC,它的锁粒度更小,性能更好,至于RC不能解决的 不可重复度 问题,根本不用解决。因为已经提交的数据默认都是有效的,被读出来无伤大雅。Oracle的默认隔离级别就是RC,它都不怕。

最后一个,在RC级别上,mysql主从复制的binlog格式使用 row,基于行的复制。嗯,InnoDB的创始人也推荐这种格式。

  • 17
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
是的,InnoDB默认隔离级别下(REPEATABLE READ)存在幻读的问题。 幻读是指在同一个事务内,多次执行相同的查询,但结果集却不一致的情况。这是由于其他事务在查询期间插入或删除了满足查询条件的行导致的。 为了解决幻读问题,InnoDB引入了多版本并发控制(Multi-Version Concurrency Control,简称MVCC)机制,并提供了两种解决幻读问题的方式: 1. 快照读(Snapshot Read):在REPEATABLE READ隔离级别下,默认使用快照读。快照读会在事务开始时创建一个一致性视图,并使用该视图来读取数据。其他事务对数据的修改不会影响当前事务的读取操作,从而避免了幻读问题。 2. 当前读(Current Read):在REPEATABLE READ隔离级别下,可以使用当前读来解决幻读问题。当前读会对查询的数据加锁,确保其他事务不能插入或删除符合查询条件的行。可以使用SELECT ... FOR UPDATE语句或SELECT ... LOCK IN SHARE MODE语句来进行当前读操作。 需要注意的是,REPEATABLE READ隔离级别下,使用快照读可以避免大部分幻读问题,但在某些情况下仍然可能出现幻读。如果需要完全避免幻读,可以将隔离级别提升至SERIALIZABLE,但这可能会影响并发性能。 总结起来,InnoDB通过MVCC机制和快照读、当前读的方式来解决幻读问题。开发者可以根据具体的业务需求和性能要求选择适当的隔离级别和读取方式来处理幻读问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值