Java 面试题:事务隔离级别以及并行事务会出现什么问题怎么解决脏读、不可重复读和幻读问题 --xunznux
文章目录
- 四种事务隔离级别
- MySQL中设置事务隔离级别
- 四种事务隔离级别在并行事务中可能会遇到的问题
- 脏读、不可重复读和幻读
- 三者区别
- 事务的隔离级别是怎么解决这三个问题的?
- Read View 是什么
- Read View 包含的信息
- Read View 在 MVCC 中的工作原理
- 工作流程
- 总结
事务的隔离级别定义了多个事务之间的可见性和操作顺序,确保数据的完整性和一致性。SQL标准定义了四种隔离级别,每种隔离级别都对事务之间的干扰程度进行了不同的限制,防止脏读、不可重复读和幻读等问题。
四种事务隔离级别
-
未提交读(Read Uncommitted)
- 描述:在此级别下,一个事务可以读取其他事务未提交的数据。这种隔离级别可能导致脏读问题,即读取到的值可能是其他事务修改但未提交的值。
- 缺点:可能发生脏读。
- 优点:性能最高。
-
已提交读(Read Committed)
- 描述:在此级别下,一个事务只能读取到其他事务已提交的数据。未提交的更改对于其他事务不可见。这种级别避免了脏读,但可能会发生不可重复读。
- 缺点:可能发生不可重复读。
- 优点:大多数数据库的默认隔离级别(如Oracle)。
-
可重复读(Repeatable Read)
- 描述:在此级别下,一个事务在开始时读取的数据,在整个事务期间都不会改变,即使其他事务进行了提交。这样可以避免不可重复读,但可能会发生幻读。
- 缺点:可能发生幻读。
- 优点:MySQL的默认隔离级别,能保证更高的事务一致性。
-
可串行化(Serializable)
- 描述:这是最高的隔离级别,所有的事务依次执行,就像它们是串行化的一样。在这种情况下,可以完全避免脏读、不可重复读和幻读问题。
- 缺点:并发性最差,性能最低。
- 优点:保证事务的完整隔离。
MySQL中设置事务隔离级别
可以通过以下命令查看和设置MySQL的事务隔离级别:
-
查看当前隔离级别
SELECT @@global.tx_isolation;
或者
SHOW VARIABLES LIKE 'transaction_isolation';
-
设置事务隔离级别
SET GLOBAL TRANSACTION ISOLATION LEVEL {level};-- 设置会话隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL {level};-- 设置全局隔离级别为可重复读
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;-- 设置当前会话隔离级别为可重复读
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
隔离级别可以是 'READ UNCOMMITTED'
, 'READ COMMITTED'
, 'REPEATABLE READ'
, 'SERIALIZABLE'
之一。
通过以上设置,可以在MySQL中调整事务的隔离级别,以满足不同应用场景的需求。
四种事务隔离级别在并行事务中可能会遇到的问题
隔离级别 | 脏读 (Dirty Read) | 不可重复读 (Non-repeatable Read) | 幻读 (Phantom Read) |
---|---|---|---|
读未提交 (Read Uncommitted) | 可能发生 | 可能发生 | 可能发生 |
读已提交 (Read Committed) | 不会发生 | 可能发生 | 可能发生 |
可重复读 (Repeatable Read) | 不会发生 | 不会发生 | 可能发生 |
可串行化 (Serializable) | 不会发生 | 不会发生 | 不会发生 |
脏读、不可重复读和幻读
- 脏读 (Dirty Read): 事务A读取了事务B未提交的数据,之后事务B回滚,导致事务A读取到了无效数据。
- 不可重复读 (Non-repeatable Read): 事务A在读取同一行数据的不同时间点,得到的结果不同。原因是事务B在事务A的两次读取期间修改并提交了该行数据。
- 幻读 (Phantom Read): 事务A在两次查询之间,事务B插入或删除了数据,导致事务A的两次查询结果行数不同。
三者区别
- 脏读:重点在于读到了无效数据,原因是在读取最新数据后,这个最新数据被其他事务回滚了,脏读再次读取最新数据时无法再读取这一个数据了。数据是从有到无得变化。
- 不可重复读:重点在于前后两次读取的同一行数据得到内容不同。原因是数据的更新,从版本1变化到版本2。
- 幻读:重点在于两次查询一种类型的数据时,查询结果行数不一致。原因是其他事务对这个类型的数据集合增加了新的数据或者删掉了旧的某些行数据,从集合A变成集合B。
事务的隔离级别是怎么解决这三个问题的?
- 读未提交每次读取最新数据,没有解决这三个问题。是最低的隔离级别(没有隔离)。
- 读已提交,一个事务A只能读取到其他事务B已经提交(commit)的数据,事务B(transaction)在开始(begin)之后,它在事务期间执行的SQL都会产生结果,但是如果没有提交(commit)事务。在这个隔离级别下,A就无法读取到B的中间结果,因此,如果B在最后没有提交,而是回滚(rollback)事务,那么也不会出现脏读。但是,如果在事务A期间执行了两次查询,第一次查询查到了事务B对数据 a 的插入结果,第二次查询查到了事务C对数据a的更新结果。因此两次查询a的数据不一样,出现了不可重复读的问题。
- 原因:读已提交是通过read view实现的。它在每次 select 时都会生成一个 Read View,因此事务A期间的两次读取同⼀条数据,前后两次读的数据会出现不⼀致,因为这期间另外⼀个事务C修改了事务B之前提交的 a 记录,并提交了事务C。
- 可重复读:可重复读(Repeatable Read)隔离级别的实现依赖于多版本并发控制(MVCC)。当事务启动时,会生成一个 Read View,这个 Read View 确保了在整个事务期间,普通的快照读(Snapshot Read)总是读取事务启动时看到的数据,即事务启动前的数据快照。因此,也就解决了不可重复度的问题。注意:select … for update 属于当前读,因此不会使用快照读的read View,而是读取最新的数据,如果因为这个导致前后读取数据不一致的问题,不属于不可重复读。
- 幻读:可重复读隔离级别仍然没有完全解决幻读问题。为什么这么说呢?如果在事务A启动后连续执行了两次普通的快照读。那么这两次查询不会发生幻读。但是如果在这两次查询期间,事务B插入了一条数据,然后事务A在第二次查询之前,尽管它第一次没有查到这条数据,但是仍然对它进行了更新操作,那么这条数据的 trx_id 就是A的 trx_id,因此,第二次查询就会查到这一条数据。发生了幻读。第二个例子:两次查询中,第一次使用快照读,第二次使用了当前读(select for update),如果在这期间其他事务B插入了新的数据并且提交,这个数据又刚好满足A的查询条件,由于当前读不会使用隔离级别生成的ReadView,而是使用最新的,就会发生幻读问题。
- 怎么完全解决:通过串行化隔离级别(最高隔离),没有并行就不会出现并发问题,串行化隔离级别通过排它锁阻止了其他事务对当前事务所查询数据区间的修改。
Read View 是什么
Read View 是 MySQL 的 InnoDB 存储引擎在实现多版本并发控制(MVCC)时的一个关键概念。它用于确保在一个事务的整个生命周期内,快照读操作看到的数据是一致的。以下是 Read View 包含的信息及其在 MVCC 中的工作原理:
Read View 包含的信息
-
m_ids(Min Transaction ID Set):
- 这是在 Read View 创建时系统中所有活跃事务的事务 ID 列表。一个事务被认为是活跃的,如果它的状态是已启动但尚未提交或回滚。
-
min_trx_id:
- 这是
m_ids
中最小的事务 ID(即当前系统中最早启动但未提交的事务 ID)。它用于判断数据版本是否由某个还未提交的事务生成。
- 这是
-
max_trx_id:
- 这是系统中下一个即将生成的事务 ID,它标识了在创建 Read View 时所有新启动事务的 ID 都会大于
max_trx_id
。这个值主要用于判断哪些事务的修改在 Read View 生成时还不存在。
- 这是系统中下一个即将生成的事务 ID,它标识了在创建 Read View 时所有新启动事务的 ID 都会大于
-
creator_trx_id:
- 这是创建 Read View 的当前事务的事务 ID。它用于在可见性判断中排除当前事务对数据的修改。
Read View 在 MVCC 中的工作原理
在 MVCC 中,每一行数据都包含了多个版本,每个版本关联一个事务 ID(trx_id
),标识该版本是由哪个事务创建的。Read View 的作用就是判断某个版本的数据对当前事务是否可见。
工作流程
-
读取数据时生成 Read View:
- 当一个事务第一次执行快照读(即普通的
SELECT
操作)时,InnoDB 会创建一个 Read View。
- 当一个事务第一次执行快照读(即普通的
-
判断数据版本的可见性:
- 对于每一行数据,InnoDB 会检查这个数据版本的
trx_id
。 - 如果
trx_id < min_trx_id
,说明这个数据版本是在所有活跃事务之前生成的,因此它对当前事务可见。 - 如果
trx_id >= max_trx_id
,说明这个数据版本是在 Read View 创建后生成的,因此它对当前事务不可见。 - 如果
trx_id
在min_trx_id
和max_trx_id
之间,则需要进一步判断:- 如果
trx_id
属于m_ids
集合中的某个活跃事务,说明这个版本的数据是由当前某个未提交的事务生成的,因此不可见。 - 如果
trx_id
不在m_ids
集合中,说明这个事务在 Read View 生成时已经提交,因此该版本的数据对当前事务可见。
- 如果
- 对于每一行数据,InnoDB 会检查这个数据版本的
-
读取合适的数据版本:
- InnoDB 会根据上述判断,决定使用哪个版本的数据。如果当前版本不可见,则通过
roll_pointer
指针找到上一个版本,继续判断,直到找到一个对当前事务可见的版本为止。
- InnoDB 会根据上述判断,决定使用哪个版本的数据。如果当前版本不可见,则通过
总结
Read View 是 InnoDB 实现 MVCC 的核心,它确保了在事务执行过程中快照读操作的一致性。通过维护一个活跃事务的列表和相关的事务 ID 边界,Read View 能够精确地判断哪些数据版本对当前事务可见,哪些不可见,从而避免出现脏读、不可重复读等并发问题。