什么是脏读、幻读、不可重复读?
脏读、幻读和不可重复读是数据库中常见的并发访问问题,它们描述了在多个事务并发执行时可能出现的数据读取问题。
脏读(Dirty Read)
指的是一个事务读取了另一个事务尚未提交的数据。
当事务B修改了数据但尚未提交时,事务A读取到了这个未提交的数据。如果事务B最终回滚了,那么事务A读取到的数据就是脏数据。
脏读可能导致事务A基于不正确的数据做出决策,从而产生错误的结果。
图片
不可重复读(Nonrepeatable Read)
指的是在一个事务内,多次读取同一数据时,得到的结果不一致。
例如,事务A第一次读取数据时,得到了某个值,但在事务A的执行过程中,事务B修改了这个值并提交了。
当事务A第二次读取同一数据时,得到的值与第一次读取时不同。
不可重复读可能导致事务A在同一事务内基于不一致的数据做出决策,从而产生错误的结果。
幻读(Phantom Read)
指的是在一个事务内,多次执行相同的查询时,得到的结果集不一致。
例如,事务A第一次查询时得到了一组数据,但在事务A的执行过程中,事务B插入了符合第一次查询条件的新数据并提交了。
当事务A第二次查询相同条件时,得到的结果集中出现了新增的数据,就好像产生了幻觉一样。
幻读可能导致事务A在同一事务内处理了不一致的数据集,从而产生错误的结果。
图片
重点区别一下不可重复读和幻读:
不可重复读关注,事务内读取到的,数据值内容发生的变化,而幻读关注,事务内执行相同查询时,结果集数量发生变化。
一句话总结脏读、不可重复读、幻读:
脏读:读取未提交的数据。
不可重复读:读取数据期间,数据被其他事务修改,导致再次读取时结果不同。
幻读:在范围查询期间,有其他事务插入或删除了记录,导致查询结果的数量不一致。
这些问题的出现是由于并发事务访问数据库时的隔离性不足所导致的。
如何解决这些事务访问并发问题?
为了解决这些问题,数据库系统提供了不同的事务隔离级别,不同的隔离级别提供了不同的解决方案,以确保事务的隔离性和数据的一致性。
数据库系统中的事务隔离级别有哪些?
SQL-92 标准定义了 4 种隔离级别来解决脏读、幻读、不可重复读等这些异常情况,从高到底依次为:
可串行化(Serializable)、可重复读(Repeatable reads)、读已提交(Read committed)、读未提交(Read uncommitted)。
1. 读未提交(RU):
最低的隔离级别,在这种事务隔离级别下,允许一个事务读取另一个事务尚未提交的数据。
这可能导致脏读、不可重复读和幻读的问题。
2. 读已提交(RC):
也可以翻译成提交读,在一个事务修改数据过程中,如果事务还没提交,其他事务不能读该数据。
这可以避免脏读问题,但仍可能出现不可重复读和幻读的问题。
3. 可重复读(RR):
在一个事务中,多次读取同一数据时,得到的结果保持一致。
即使其他事务对数据进行了修改并提交,当前事务读取的数据也不会发生变化。
比提交读更高一个级别的隔离级别,可重复读可以避免脏读和不可重复读问题,但仍可能出现幻读的问题。
4. 串行化(Serializable):
最高的隔离级别,确保事务之间完全隔离,一个事务执行时,其他事务无法对其进行并发操作。
串行化可以避免脏读、不可重复读和幻读的问题,但会降低并发性能。
图片
这些隔离级别按照隔离强度逐渐增强,同时也伴随着性能的降低。
选择适当的隔离级别需要根据具体的业务需求和并发访问情况进行权衡。
事务隔离级别又是如何实现的?
事务隔离级别的实现方式可以根据具体的数据库系统和存储引擎而有所不同。
1. 锁机制:
数据库系统可以使用锁来实现事务隔离级别。
通过在读取和修改数据时加锁,可以确保事务之间的隔离性。
不同的隔离级别可能使用不同类型的锁,如行级锁、表级锁或页级锁。
合理使用「共享锁」「独占锁」就可以解决事务间写入隔离的问题
图片
MySQL中利用GAP锁 和 Next-Key实现了不可重复读的隔离级别。
GAP锁用于锁定一个范围的键值之间的间隙,以防止其他事务在该范围内插入新的索引记录。
Next-Key锁是GAP锁(间隙锁)和NOT-GAP锁(精确行锁)的组合,不仅锁定了索引记录本身,还锁定了索引记录之前的间隙。
图片
这样可以避免不可重复读问题的发生,保证事务在读取范围内的数据时,其他事务不能在该范围内插入新的数据。
不过有关「读」的问题,可以使用效率更高的MVCC解决。
图片
2. 多版本并发控制(MVCC)
MVCC (Multi-Version Concurrency Control),即多版本并发控制,是一种常见的实现事务隔离级别的方式。
它通过为每个事务创建多个版本的隔离快照来实现隔离。
在快照隔离中,每个事务在开始时会创建一个数据快照,事务中的所有读取操作都基于该快照进行。
每个事务在读取数据时会看到一个一致性的快照,这个快照是在事务开始时确定的。
3.当有其他事务对数据进行修改时,MVCC又会创建一个新的数据版本,并将新版本的数据与旧版本的数据进行区分。
这样,读取操作可以读取旧版本的数据,而写入操作则会写入新版本的数据,从而实现读写操作的并发性。
MVCC在某些场景中替代了相对低效的「锁」, 可以避免脏读和不可重复读的问题。
图片
(MVCC 这块是面试的重难点,这一块我们后面还会有文章进行详细的介绍)
4. 串行化执行:
虽然江湖中有传说MVCC可以解决幻读的问题,但实际上并非如此。
在串行化隔离级别下,事务之间是串行执行的,即每个事务在执行期间都会锁定所涉及的数据表,其他事务必须等待该事务完成后才能执行。
这种方式才可以避免脏读、不可重复读和幻读的问题,但会降低并发性能。
图片
需要注意的是,不同的数据库系统和存储引擎可能会有不同的实现方式。
因此,在具体的数据库系统中,实现事务隔离级别的方式可能会有所不同。这里我们是以MySQL为例进行说明。
介绍一下MySQL中,与事务隔离级别相关的命令
MySQL中与事务隔离级别相关的命令主要有以下两个:
SET TRANSACTION ISOLATION LEVEL:
该命令用于设置当前会话的事务隔离级别。可以使用以下语法:
SET TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}
其中,READ UNCOMMITTED表示读未提交,READ COMMITTED表示读已提交,REPEATABLE READ表示可重复读,SERIALIZABLE表示串行化。
通过设置不同的隔离级别,可以控制事务的隔离性和并发访问的行为。
SELECT @@tx_isolation:
该命令用于查询当前会话的事务隔离级别。执行该命令后,会返回当前会话的事务隔离级别。
图片
MySQL默认的隔离级别 RR 可重复读。
命令行开始事务时set autocommit = off 或者 start transaction