MySQL事务与Spring事务传递机制

背景

Spring是一个非常流行的Java开发框架,其中事务管理机制是其重要的特性之一。而MySQL则是常用的关系型数据库,事务管理也是其核心功能之一。本文将从Spring事务与MySQL事务的入手,详细讲解二者之间的关系。

一、MySQL事务

数据库的多事务并发问题,为了解决多事务并发问题,数据库设计了事务隔离机制、锁机制、MVCC多版本并发控制隔离机制,用一整套机制来解决多事务并发问题

1.1 事务的属性ACID

事务(Transaction)是数据库系统中一系列操作的一个逻辑单元,所有操作要么全部成功要么全部失败。

事务是区分文件存储系统与Nosql数据库重要特性之一,其存在的意义是为了保证即使在并发情况下也能正确的执行crud操作。怎样才算是正确的呢?这时提出了事务需要保证的四个特性即ACID:

  • 原子性(Atomicity) :事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。
  • 一致性(Consistent) :在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性。
  • 隔离性(Isolation) :数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的”独立“环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。
  • 持久性(Durable) :事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。

1.2 事务的隔离级别

在高并发的情况下,要完全保证其ACID特性是非常困难的,除非把所有的事务串行化执行,但带来的负面的影响将是性能大打折扣。很多时候我们有些业务对事务的要求是不一样的,所以数据库中设计了四种隔离级别,供用户基于业务进行选择。

MySQL的事务隔离级别包括:

  • READ UNCOMMITTED(未提交读)
  • READ COMMITTED(提交读)
  • REPEATABLE READ(可重复读)
  • SERIALIZABLE(可串行化)

其中,READ COMMITTED是MySQL默认的事务隔离级别。

并发事务处理带来的问题

  • 更新丢失(Lost Update)或脏写

当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题–最后的更新覆盖了由其他事务所做的更新

  • 脏读(Dirty Reads)

一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致的状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”数据,并据此作进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象的叫做“脏读”。

简短总结:事务A读取到了事务B已经修改但尚未提交的数据,还在这个数据基础上做了操作。此时,如果B事务回滚,A读取的数据无效,不符合一致性要求。

  • 不可重读(Non-Repeatable Reads)

一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变、或某些记录已经被删除了!这种现象就叫做“不可重复读”。

简短总结:事务A内部的相同查询语句在不同时刻读出的结果不一致,不符合隔离性

  • 幻读(Phantom Reads)

一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”。

简短总结:事务A读取到了事务B提交的新增数据,不符合隔离性

隔离级别 脏读 不可重复读 幻读
读未提交 可能 可能 可能
读已提交 X 可能 可能
可重复读 X X 可能
串行化 X X X

1.3 锁机制

锁是计算机协调多个进程或线程并发访问某一资源的机制。

在数据库中,除了传统的计算资源(如CPU、RAM、I/O等)的争用以外,数据也是一种供需要用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。

锁分类

  • 从性能上分为乐观锁(用版本对比来实现)和悲观锁

  • 从对数据库操作的类型分,分为读锁和写锁(都属于悲观锁)

    • 读锁(共享锁,S锁(Shared)):针对同一份数据,多个读操作可以同时进行而不会互相影响

    • 写锁(排它锁,X锁(eXclusive)):当前写操作没有完成前,它会阻断其他写锁和读锁

  • 从对数据操作的粒度分,分为表锁和行锁

1.4 WAL原则

InnoDB的ARIES三原则Write Ahead Logging(WAL):

  • 日志成功写入后事务就不会丢失,后续由checkpoint机制来保证磁盘物理文件与redo log达到一致性;
  • 利用redo log来记录变更后的数据,即redo里记录事务数据变更后的值;
  • 利用undo log来记录变更前的数据,即undo里记录事务数据变更前的值,用于回滚和其他事务多版本读。

1.5 MVCC

MVCC(Multi-Version Concurrency Control)是一种并发控制技术,用于在数据库系统中处理多个并发事务。在 MySQL 中,MVCC 机制用于支持读已提交和可重复读两种隔离级别的实现。

MVCC 的核心思想是:对于正在执行的事务,数据库系统为其创建一个独立的视图(快照),让事务基于该视图进行操作。这个视图包含了事务启动时数据库中所有已提交的数据版本,但不包含当前正在进行的事务的更新数据。对于正在执行的事务,只能看到早于该事务启动时的数据版本,从而实现了并发控制。

在 MVCC 中,每条记录都有多个版本。对于一个新插入的记录,其版本号为 1;每次该记录被更新,其版本号都会自增。每个事务启动时,MySQL 会为该事务创建一个唯一的事务 ID(transaction ID),也称为版本号。在事务执行期间,MySQL 会在事务视图中为每条记录分配一个版本号,该版本号是其创建或更新时的版本号,以及该事务的版本号(事务 ID)的组合。因此,对于每个事务而言,它所看到的所有记录版本都是唯一的。

1.6 事务日志

重做日志(redo log)

重做日志(redo log)是InnoDB引擎层的日志,用来记录事务操作引起数据的变化,记录的是数据页的物理修改。

重做日记的作用其实很好理解,我打个比方。数据库中数据的修改就好比你写的论文,万一哪天论文丢了怎么呢?以防这种不幸的发生,我们可以在写论文的时候,每一次修改都拿个小本本记录一下,记录什么时间对某一页进行了怎么样的修改。这就是重做日志。

InnoDB引擎对数据的更新,是先将更新记录写入redo log日志,然后会在系统空闲的时候或者是按照设定的更新策略再将日志中的内容更新到磁盘之中。这就是所谓的预写式技术(Write Ahead logging)。这种技术可以大大减少IO操作的频率,提升数据刷新的效率。

回滚日志(undo log)

回滚日志同样也是InnoDB引擎提供的日志,顾名思义,回滚日志的作用就是对数据进行回滚。当事务对数据库进行修改,InnoDB引擎不仅会记录redo log,还会生成对应的undo log日志;如果事务执行失败或调用了rollback,导致事务需要回滚,就可以利用undo log中的信息将数据回滚到修改之前的样子。

但是undo log不redo log不一样,它属于逻辑日志。它对SQL语句执行相关的信息进行记录。当发生回滚时,InnoDB引擎会根据undo log日志中的记录做与之前相反的工作。比如对于每个数据插入操作(insert),回滚时会执行数据删除操作(delete);对于每个数据删除操作(delete),回滚时会执行数据插入操作(insert);对于每个数据更新操作(update),回滚时会执行一个相反的数据更新操作(update),把数据改回去。undo log由两个作用,一是提供回滚,二是实现MVCC。

二、Spring事务传递机制

Spring的事务传递机制指的是在多个事务方法嵌套调用时,事务如何传递的规则。Spring提供了以下7种事务传递机制:

  1. PROPAGATION_REQUIRED(默认):如果当前存在事务,则加入该事务;如果当前不存在事务,则创建一个新的事务。
  2. PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。
  3. PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。
  4. PROPAGATION_REQUIRES_NEW:创建一个新的事务,并在该事务内运行。如果当前存在事务,则将当前事务挂起。
  5. PROPAGATION_NOT_SUPPORTED:以非事务方式运行。如果当前存在事务,则将当前事务挂起。
  6. PROPAGATION_NEVER:以非事务方式运行。如果当前存在事务,则抛出异常。
  7. PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内运行;如果当前不存在事务,则创建一个新的事务。

三、MySQL事务与Spring事务传递机制的联系

Spring事务传播机制与MySQL事务管理的关系在于,Spring提供了对事务的抽象和封装,简化了事务的管理。

在使用Spring的事务管理功能时,可以通过配置 propagation 属性来定义事务的传播机制,从而控制事务的行为。

而MySQL的事务管理则更加底层,需要开发人员手动管理事务和锁,需要考虑更多的细节和问题。

不过,Spring事务管理的底层仍然是通过JDBC来实现的,因此还是需要理解MySQL事务管理的相关知识才能更好地使用Spring事务管理功能。

0%