MVCC——一个我特别不想聊的技术

本文详细介绍了MVCC多版本并发控制的概念、在并发控制中的作用、核心机制、组件如UndoLog和ReadView,以及在Java应用中的Spring集成。讨论了不同隔离级别对MVCC的影响,并给出了在事务管理和并发优化中的实践应用。
摘要由CSDN通过智能技术生成


前言

提示:此处涉及到的点比较多,若是有什么疑问点欢迎下方交流
我最开始仅仅是在spring层面进行使用Mysql的事务,但是后来不知道什么时候忽然被面试官问到这个问题了,没办法问到了就整理一下吧。
整理了三天,总算是出来了,但是还没有到自己想要的那么精简
so,有好的意见记得找我哦


一、MVCC?官方一些解释

MVCC全称为Multi-Version Concurrency Control即多版本并发控制。
它是一种用于数据库管理系统中的并发控制的技术,
在许多数据库系统(如MySQL的InnoDB存储引擎、PostgreSQL等)中得到使用。

MVCC通过为数据库对象(如行记录)提供多个版本来实现在没有锁定资源的情况下进行并发访问,增强了读写(这两个是一个整体)操作的并发性能。

但是在我看来就是一个思想,我们实际上用到的就是一个读数据的级别(当前读还是快照读)和同时操作多张表的原子性要么都操作成功,要么都操作失败

二、介绍MVCC

1.并发控制的必要性

1、并发控制是数据库管理系统同步多个事务的行为
2、避免由于事务之间的相互影响引起的一系列问题。
如果不妥善处理,并发操作可能会导致数据不一致,
例如脏读(读取未提交的数据)。
不可重复读(一个事务内两次及以上读取不一致)。
幻读(同一个事务两次及以上读取时出现新的行)。
补充:其实我也搞不懂为什么要查询两次或者多次在一个事务中?
但是:MVCC主要是解决读-写和写-读场景下的并发问题,而不是写-写场景,写-写通常还需要依赖锁机制。

2. MVCC的定义及目的

MVCC是一种用来控制数据库并发访问的技术,允许不同的事务看到某个时间点的数据快照。MVCC的主要优势是它可以在不加锁的情况下使读取操作并行进行,从而大大提高了事务的处理速度,同时维护了数据的一致性。
这里的一致是在某些场景下(如RR)的同一个事务内的一致。

MVCC的优势在于:

  1. 减少了锁的需求:读取操作通常不需要锁定资源,这样减少了锁竞争,提高了并发度。
  2. 读写分离:读写操作能够相互独立进行,因此在很多情况下,对数据的写入(更新、删除)不影响到数据的读取。
  3. 避免了读写冲突:读者不会阻塞写者,写者也不会阻塞读者。(因为读取的就不是一个地方)

补充:在MySQL的InnoDB存储引擎中每行记录都有隐藏的系统列如下:
DB_TRX_ID(最近修改事务的ID)
DB_ROLL_PTR(撤回指针,指向undo log的记录)
DB_ROW_ID(当未定义聚集索引时的隐藏的行ID)
以支持MVCC的实现。当你在执行SELECT、UPDATE、DELETE等操作时,InnoDB会通过MVCC机制确保每个事务都能正确地看到它应该看到的数据版本。

3、MVCC的核心机制和组件

Undo Log版本链:

当数据被修改时,其原版本并不会立即被覆盖,而是被记录在Undo Log中并生成版本链(类似于双向裂变)。如果需要,可以通过Undo Log找回之前的数据版本。这个机制使得数据库能够支持事务的回滚以及提供旧数据版本给不同的事务访问。

ReadView:

在事务开始时,系统会生成一个ReadView,其中记录了活跃的事务及其ID。ReadView确保了在事务执行期间,即使外部数据发生变化,事务也能继续看到一致的数据视图。还有最新的事务id以及最小事务id

4. 隔离级别与MVCC

补充:不同的隔离级别下MVCC的业务流程吧同?

Read Committed:在这个级别下,事务会在每次查询时见到其他事务已经提交的最新数据。(快照读)
Repeatable Read:MySQL的默认隔离级别,可以确保一个事务从开始到结束都看到相同的数据快照,即使其他事务进行了提交。

不同隔离级别通过MVCC的影响如下:

READ UNCOMMITTED :MVCC机制基本不起作用,因为这个级别几乎不提供任何并发保护。
READ COMMITTED : MVCC 通过返回最新提交版本的数据来避免脏读。
REPEATABLE READ : MVCC生成时间点快照来确保事务的不可重复读取问
SERIALIZABLE : 尽管通常需要锁来实现完全的事务隔离,MVCC仍然可以提高只读事务的并发性。

5.MVCC在查询和更新操作中的表现

查询操作:MVCC使得执行查询时不必等待其他事务完成,因为它可以从适当版本的数据中读取信息。(快照读)

不同隔离级别下读取的时机不同

更新操作:虽然MVCC在增强读操作的并发性方面表现突出,但在更新数据时,为避免修改冲突,仍需要对数据进行锁定。

事务中的查询操作会通过MVCC机制来保证读取数据的正确性,而修改操作(更新、删除等)会在Undo Log中记录变更,生成数据的新版本,并在最终提交时更新数据。
在REPEATABLE READ隔离级别下,一个事务在它的生命周期内会看到一致的数据快照,而在READ COMMITTED级别下,每次读取都可能返回最新的数据版本。修改操作期间,MVCC并不一定会加锁,而是依赖于版本链来解决数据一致性问题。锁机制(如行锁)通常用于解决更新冲突

6. 在Java应用中使用MVCC

Spring框架中的@Transactional注解为开发者提供了一种简便的方式来申明和管理事务。
通过这个注解,我们可以指定事务的隔离级别,从而在应用级别利用数据库的MVCC机制来提升并发性能和数据一致性。
我们实际在项目中使用的时候用到spring中的注解@Translation,最主要的目的就是:
1、读取数据的正确性:读取已提交还是可重复读
2、原子性:要么全部成功,要么全部失败,不会出现部分成功的情况
补充:spring其实并不存在事务,他所描述的事务依旧是Mysql中的事务
在Spring中管理事务时,通常是通过Spring的事务抽象层来实现的,它遵循了ACID原则:
原子性(Atomicity):Spring事务确保全部操作要么都成功,要么都失败,如果在事务中的任何地方发生异常,所有操作都将回滚到事务开始之前的状态。
一致性(Consistency):通过事务的成功提交,保证数据从一个一致的状态转变到另一个一致的状态。
隔离性(Isolation):隔离性级别可以配置,Spring支持不同的事务隔离级别,包括READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ和SERIALIZABLE,以处理不同程度的并发问题。
持久性(Durability):一旦事务被提交,它对数据库所做的修改将持久保留,即使在系统发生故障后也能保留。

代码示例

import org.springframework.transaction.annotation.Transactional;

@Transactional
public void updateMultipleTables() {
  // 修改表1
  // 修改表2
  // ...
  // 如果这里发生异常,之前的所有修改将会回滚
}
在这个updateMultipleTables方法中,
如果对表1的修改成功了,但是后面对另一张表的修改发生了异常,
Spring将会回滚整个事务,这样在方法的外观表现上确实就是“要么都成功,要么都失败”。
Spring底层使用AOP(面向切面编程)来创建代理并围绕
@Transactional标注的方法执行事务管理代码。
事务管理器负责为事务操作实际创建和管理事务。
在Spring中,这可以是任何遵循Spring事务抽象的事务管理器
例如,对于JDBC使用DataSourceTransactionManager,
对于JPA则可能使用JpaTransactionManager。
使用@Transactional的目的是为了确保数据读取的一致性和事务的原子性。
优化:@Transactional注解提供了事务的声明式管理,它可以定义事务的传播行为、
隔离级别、回滚规则等属性。这个注解确保了方法的执行在一个事务上下文中,
如果发生异常则能够自动回滚所有变更。
然后就是事务的传播,很鸡肋的一件事情,感觉没啥价值,也可能是没有遇到那样的场景

很重要的点:
1、什么时候事务会失效
2、事务方法内最好仅仅使用操作数据库方法,不要有RPC接口调用等等耗时操作

@Transactional属性:

1、value / transactionManager:
指定使用的事务管理器。如果配置了多个事务管理器,可以使用这个属性来选择一个特定的事务管理器来管理当前事务。
例如:@Transactional(“txManager”)
2、propagation:
定义事务的传播行为。这决定了事务方法是如何关联到已存在的事务上。
常用的传播行为包括 REQUIRED(默认值,如果当前存在事务则加入,否则新建一个)、REQUIRES_NEW(总是新建事务)、NEVER(不应在事务中运行,否则抛出异常)等。
3、isolation:
定义事务的隔离级别。这决定了一个事务可能受其他并发事务影响的程度。
隔离级别包括 READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ、SERIALIZABLE。
4、timeout:
定义事务的超时时间,默认是-1(表示无超时限制)。如果事务执行时间超过这个设定的时间,就会回滚事务。
例如:@Transactional(timeout = 10) // 10秒
5、readOnly:
表示事务是否为只读事务。如果设置为true,这表明该事务只读取数据但不进行修改,这可能帮助数据库引擎优化事务。
例如:@Transactional(readOnly = true)
6、rollbackFor / rollbackForClassName:
定义哪些异常可以触发事务回滚。默认情况下,事务只会在运行时异常(继承自RuntimeException)和错误(Error)发生时回滚。
例如:@Transactional(rollbackFor = {Exception.class})
7、noRollbackFor / noRollbackForClassName:
与 rollbackFor 相反,定义哪些异常发生时,事务不应回滚。
例如:@Transactional(noRollbackFor = {CustomException.class})
8、label:
事务的标签名,在日志中使用,以帮助识别事务。
使用@Transactional注解时,可以根据具体情况组合使用这些属性,从而准确控制事务的行为和性能特性。适当的配置可以大大提升应用程序在数据一致性和并发处理上的表现。

7. 总结和实践应用

MVCC技术可以极大地提升数据库在高并发环境下的性能,并确保了数据的一致性。但在选用时,需要根据应用的具体要求权衡隔离级别的选择。一般来说,开发者应该选择能满足业务要求的最低隔离级别,以此来平衡性能和数据一致性之间的关系。

补充:

1、ReadView拓展:

在MVCC(多版本并发控制)中,ReadView是用来实现一致性非锁定读取的关键结构。当一个事务进行读取操作时,它会创建或获取一个ReadView,用于确定哪些数据版本对该事务可见。这确保在可重复读或读已提交隔离级别下,事务可以获得一致的数据视图。

一个ReadView通常包含以下属性:

creator_transaction_id:
用来记录创建该ReadView的事务的事务ID。
作用:标识是哪个事务创建了ReadView,有助于区分数据版本是否由当前事务生成。
up_limit_id / low_limit_id:
即当前ReadView时,已经提交的最大事务ID(低限)和下一个可能分配的事务ID(上限)。
作用:任何大于上限ID的事务修改,或小于下限ID的事务(如果未提交则不可见),都不会被当前事务所看到。

min_trx_id:
创建该ReadView时,活跃事务中最小的事务ID。
作用:这有助于识别在创建ReadView之后开始的事务,因为这些事务的ID会大于min_trx_id。

max_trx_id:
创建该ReadView时,活跃事务中最大的事务ID(通常是已分配的最大事务ID)。
作用:这帮助确定哪些事务是在创建ReadView之前就已经存在的。
trx_ids:
创建该ReadView时,当前正在进行中的所有未提交事务的事务ID列表。
作用:通过这个列表,MVCC能够决定对当前事务哪些数据版本是不可见的。因为这些事务可能对数据有未提交的修改,所以当前事务不应该看到这些修改。

这些属性共同工作,让事务可以根据生成ReadView时刻的数据库事务状态来决定数据的可见性。当一个事务要访问某行数据时,它会检查该行数据的DB_TRX_ID(该行最近一次被修改的事务ID),然后通过ReadView属性来判断该数据版本是否可见:
如果DB_TRX_ID在trx_ids列表中,表示该事务还未提交,当前事务不能看到这个版本。
如果DB_TRX_ID大于等于up_limit_id,表示该版本是在ReadView创建后产生的,当前事务不能看到这个版本。
如果DB_TRX_ID小于low_limit_id,表示该版本是ReadView创建前已提交的,因此对当前事务来说是可见的。
ReadView是MVCC提供一致性读取和事务隔离的重要机制,使得不同的事务可以“看到”数据库在某一时间点的状态,避免了数据在读取过程中可能出现的不一致性。

2、核心:

关于数据库中表添加一个字段version作为CAS(乐观锁)的版本号来进行保证更新的准确性和更新的安全性
查询的时候获取当前version,修改的时候改version作为where条件进行修改,并重新更新该字段’

  • 24
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值