参考:
脏读、幻读和不可重复读_脏读
全网最详细MVCC讲解,一篇看懂 - 知乎全网最详细MVCC讲解,一篇看懂 - 知乎
面试官:MySQL 的默认隔离级别是什么?可以解决幻读问题吗?
目录
一、脏读、幻读、不可重复读
二、数据库的隔离机制
(一)数据库通过锁机制解决并发访问的问题
(二)SQL 标准定义了四个隔离级别
1.READ-UNCOMMITTED(读取未提交)
2.READ-COMMITTED(读取已提交)
3.REPEATABLE-READ(可重复读)
4.SERIALIZABLE(可串行化)
三、REPEATABLE-READ(可重复读)解决幻读问题
(一)MVCC机制
一、脏读、幻读、不可重复读
脏读(读取未提交数据)
A事务先读取B事务尚未提交的数据,此时如果B事务这些尚未提交的数据发生错误并执行回滚操作,把数据恢复成原来的,而事务A却什么都不知道,那么A事务读取到的数据就是脏数据。
幻读(前后多次读取,数据总量不一致)
事务A在执行读取操作,需要两次统计数据的总量,前一次查询数据总量后,此时事务B执行了新增数据的操作并提交后,这个时候事务A读取的数据总量和之前统计的不一样,就像产生了幻觉一样,平白无故的多了几条数据,成为幻读。
幻读指的是两次查询(可能对象是
n条记录
)获取的结果集不同(因为另一个事务新增或者删除记录行)幻读侧重的是数据的条数发生了变化,原本不存在的数据存在了或原本存在的数据不存在了。
不可重复读(前后多次读取,数据内容不一致)
事务A在执行读取操作,由整个事务A比较大,前后读取同一条数据需要经历很长的时间 。而在事务A第一次读取数据,比如此时读取了小明的年龄为20岁,事务B执行修改操作,将小明的年龄更改为30岁,此时事务A第二次读取到小明的年龄时,发现其年龄是30岁,和之前的数据不一样了,也就是数据不重复了,系统不可以读取到重复的数据,成为不可重复读。
不可重复度主要是对
一条数据行
查询,同样的条件,你读取过的数据,再次读取出来发现值不一样了进行查询。不可重复读侧重数据的内容发生了变化,原本存在的数据的内容发生了改变。
二、数据库的隔离机制
(一)数据库通过锁机制解决并发访问的问题
- 根据锁定对象不同:分为行级锁和表级锁;
- 根据并发事务锁定的关系上看:分为共享锁定和独占锁定:共享锁定会防止独占锁定但允许其他的共享锁定。而独占锁定既防止共享锁定也防止其他独占锁定。为了更改数据,数据库必须在进行更改的行上施加行独占锁定,insert、update、delete和selsct for update语句都会隐式采用必要的行锁定。
但是直接使用锁机制管理是很复杂的,基于锁机制,数据库给用户提供了不同的事务隔离级别,只要设置了事务隔离级别,数据库就会分析事务中的sql语句然后自动选择合适的锁。
(二)SQL 标准定义了四个隔离级别
1.READ-UNCOMMITTED(读取未提交)
最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
一个事务已经开始写数据,则另外一个事务则不允许同时进行写操作,但允许其他事务读此行数据。该隔离级别可以通过“排他写锁”实现。这样就避免了更新丢失,却可能出现脏读。也就是说事务B读取到了事务A未提交的数据。
2.READ-COMMITTED(读取已提交)
允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。
3.REPEATABLE-READ(可重复读)
---MySQL InnoDB 存储引擎的默认支持的隔离级别
对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
在一个事务内,多次读同一数据。在A事务还没有结束时,B事务也访问该同一数据。那么,在A事务中的两次读数据之间,即使B事务对数据进行修改,A事务两次读到的的数据是一样的。这样就发生了在一个事务内两次读到的数据是一样的,因此称为是可重复读。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。这样避免了不可重复读取和脏读,但是有时可能出现幻象读。
4.SERIALIZABLE(可串行化)
最高的隔离级别,完全服从 ACID(事务四大特性) 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行。
事务的隔离级别和数据库并发性是成反比的,隔离级别越高,并发性越低。
三、REPEATABLE-READ(可重复读)解决幻读问题
标准的 SQL 隔离级别定义里,REPEATABLE-READ(可重复读)是不可以防止幻读的。
但是!InnoDB 实现的 REPEATABLE-READ 隔离级别其实是可以解决幻读问题发生的,主要有下面两种情况:
快照读:由 MVCC 机制来保证不出现幻读。
当前读:使用 Next-Key Lock 进行加锁来保证不出现幻读,Next-Key Lock 是行锁(Record Lock)和间隙锁(Gap Lock)的结合,行锁只能锁住已经存在的行,为了避免插入新行,需要依赖间隙锁。
因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是 READ-COMMITTED ,但是InnoDB 存储引擎默认使用 REPEATABLE-READ 并不会有任何性能损失。
(一)MVCC机制
全称 Multi-Version Concurrency Control,即多版本并发控制
- 主要目的是为了提高数据库并发性能,用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁(降低死锁风险)。
- 多版本指的是数据库中同时存在多个版本的数据,并不是整个数据库的多个版本,而是某一条记录的多个版本同时存在。
1.当前读和快照读
当前读
在MySQL中,当前读是一种读取数据的操作方式,它可以直接读取最新的数据版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。MySQL提供了两种实现当前读的机制:
- 一致性读(Consistent Read):
- 默认隔离级别(可重复读)下,MySQL使用一致性读来实现当前读。
- 在事务开始时,MySQL会创建一个一致性视图(Consistent View),该视图反映了事务开始时刻数据库的快照。
- 在事务执行期间,无论其他事务对数据进行了何种修改,事务始终使用一致性视图来读取数据。
- 这样可以保证在同一个事务内多次查询返回的结果是一致的,从而实现了当前读。
- 锁定读(Locking Read):
- 锁定读是一种特殊情况下的当前读方式,在某些场景下使用。
- 当使用锁定读时,MySQL会在执行读取操作前获取共享锁或排他锁,以确保数据的一致性。
- 共享锁(Shared Lock)允许多个事务同时读取同一数据,而排他锁(Exclusive Lock)则阻止其他事务读取或写入该数据。
- 锁定读适用于需要严格控制并发访问的场景,但由于加锁带来的性能开销较大,建议仅在必要时使用。
当前读实际上是一种加锁的操作,是悲观锁的实现。
快照读
快照读是在读取数据时读取一个一致性视图中的数据,MySQL使用 MVCC 机制来支持快照读。
- 每个事务在开始时会创建一个一致性视图(Consistent View),该视图反映了事务开始时刻数据库的快照。这个一致性视图会记录当前事务开始时已经提交的数据版本。
- 当执行查询操作时,MySQL会根据事务的一致性视图来决定可见的数据版本。只有那些在事务开始之前已经提交的数据版本才是可见的,未提交的数据或在事务开始后修改的数据则对当前事务不可见。
- 所以,快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本。
注意:快照读的前提是隔离级别不是串行级别,在串行级别下,事务之间完全串行执行,快照读会退化为当前读
MVCC主要就是为了实现读-写冲突不加锁,而这个读指的就是快照读,是乐观锁的实现。
2.一致性视图
只针对 RC (读已提交)和 RR(可重复读)级别,不针对 RU级别 和 Serializable级别。
主要原因是:
- Read Uncommitted(RU)隔离级别: 在 RU 隔离级别下,事务可以读取其他事务尚未提交的数据,即脏读。这意味着不需要通过 一致性视图 来限制访问范围,事务可以自由地读取其他事务的未提交数据。由于没有对可见性进行严格控制,因此不需要创建或使用 一致性视图。
- Serializable(串行化)隔离级别: 在 Serializable 隔离级别下,事务具有最高的隔离性,确保每次读取都能看到一致的快照。为了实现这种隔离级别,MySQL使用锁机制来保证事务之间的串行执行。由于事务按顺序执行,并且不允许并发操作,所以不需要使用 一致性视图 进行可见性判断。