事务特性

实现事务必须满足以下四大特性:

  • Atomicity(原子性):构成事务的的所有操作必须是一个逻辑单元,要么全部执行,要么全部不执行。
  • Consistency(一致性):数据库在事务执行前后,完整性没有被破坏。 (转账前后,钱的总数不变)
  • Durability(持久性):事务执行成功后必须全部写入磁盘。
  • Isolation(隔离性):允许多个并发事务同时对数据进行操作,也不会由于交叉执行导致数据不一致。

隔离性 通过MVVC实现(相对应的有MVCC)

原子性、一致性、持久性:通过数据库的redo log、undo log、Force Log at Commit实现

数据库是可以控制事务的传播和隔离级别的,Spring在之上又进一步做了封装,本质上是同一概念。

本文不对数据库事务做过多发散,主要介绍Spring事务相关的部分,数据库事务详情放在另外知识点。

事务隔离级别

事务的四大特性之一:隔离性 ,隔离性在数据库事务中有四种级别。

而Spring则在其基础上加了一种DEFAULT,这是PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。

其他四种隔离级别如下:

spring中如何保证ID的一致性 spring保证事务的一致性_数据

假设有两个事务交替执行,分别为事务A、事务B

READ_UNCOMMITTED未提交读

事务A做了数据的更改,而事务B能看到事务A未提交的更新数据;

这种隔离级别可能会产生脏读,比如事务A把数据更改撤销了。

READ_COMMITTED提交读

事务A做了数据的更改,事务B只有等事务A提交更改数据后,才能读到新数据。

这种隔离级别可能会产生不可重复读,比如事务B在事务A提交前读第一次,提交后读第二次,读到的数据可能不一致。

REPEATABLE_READ可重复读

可重复读是Mysql InnoDB默认隔离级别。

事务B开始了事务,不管事务A是否提交修改的数据,事务B多次读到的数据都是原先一致。

这种隔离级别可能会产生幻读,比如注解为id的数据:

事务A:select * from users where id = 1; //先看看id是否存在

事务B:insert into users(id, name) values (1, '事务B拥有'); //事务B占用了id=1

事务A:insert into users(id, name) values (1, '事务A拥有'); //事务A继续查id=1,还是空的,但是执行插入报错

事务A发生了幻读,读的是鬼影。(这里容易混淆,不可重复读侧重表达 读-读,幻读侧重表达 读-写)

SERIALIZABLE可串行化

对事务A读取到的数据进行加锁,事务B无法对该数据做修改操作,会阻塞住。只有等A事务提交事务后,修改操作才会执行。

隔离级别越高,对并发的影响就越大。可串行化容易导致超时和锁争用问题。

事务隔离级别设置

在Spring种,通过@Transactional注解的isolation属性,就可以设置当前方法的事务级别

spring中如何保证ID的一致性 spring保证事务的一致性_数据_02

Spring在开启事务时,会对当前会话设置事务隔离级别;所以当Spring设置的隔离级别和数据库的隔离级别不一致,Spring的隔离级别会生效。

Spring事务传播机制

Spring通过 AOP环绕通知进行拦截 来实现事务,我们不需要关心事务的开始、提交、回滚,只需要在方法上添加@Transactional注解就可以了。

但是带事务的方法A调用普通方法B,算不算同一个事务呢?异常了是否会一起回滚呢?诸如此类问题,事务传播机制就能解决。

通过@Transactional注解的propagation=Propagation.XXXX属性,就可以设置事务传播机制。机制类别有以下:

假设 方法A 调用了 方法B

REQUIRED(默认机制)

spring中如何保证ID的一致性 spring保证事务的一致性_数据_03

  • 方法B设置了REQUIRED,如果方法A有事务,方法B就加入当前事务,合并为一个事务(如上图)
  • 如果方法A没有事务,方法B则会自己新键一个事务

SUPPORTS

  • 方法B设置了SUPPORTS,如果方法A有事务,方法B就加入当前事务,合并为一个事务
  • 如果方法A没有事务,则方法B不开启事务

NOT_SUPPORTED

  • 方法B设置了NOT_SUPPORTED,如果方法A有事务,方法A的事务会被挂起。方法B以非事务的状态执行完,再继续执行方法A的事务。
  • 用于缩小事务范围的场景,将一些无需事务包围的代码写到NOT_SUPPORTED级别的方法中。

MANDATORY

  • 方法B设置了MANDATORY,如果方法A有事务,方法B就加入当前事务,合并为一个事务
  • 如果方法A没有事务,则会抛出异常,保证上下文调用代码不要遗漏事务。

NEVER

spring中如何保证ID的一致性 spring保证事务的一致性_事务_04

  • 方法B设置了NEVER,如果方法A有事务,则抛出异常

REQUIRES_NEW

spring中如何保证ID的一致性 spring保证事务的一致性_隔离级别_05

  • 方法B设置了REQUIRES_NEW,如果方法A有事务,方法A的事务会被挂起。方法B创建一个新的事务执行,执行完后再继续执行方法A的事务。
  • 方法A和方法B是两个单独的事务,自己的事务出现问题只回滚自己的事务。

NESTED

spring中如何保证ID的一致性 spring保证事务的一致性_隔离级别_06

  • 方法B设置了NESTED,如果方法A存在事务,方法B就会称为A事务的子事务。方法B执行结束后并不会提交,等方法A结束后才提交。
  • 如果方法A没有事务,方法B则新建事务。
  • 如果方法B异常,方法A正常提交;如果方法A异常,方法B回滚。

事务失效场景

数据库引擎不支持

MyISAM不支持事务,需要事务的一般用InnoDB引擎

没有被Spring容器管理

数据源没有配置事务管理器

spring中如何保证ID的一致性 spring保证事务的一致性_隔离级别_07

非public方法

@Transactional注解只能添加到public方法上

异常被捕捉

需要抛出异常,事务才能回滚

抛出异常类型错误

默认捕捉的是RuntimeException,而抛出的为Exception

spring中如何保证ID的一致性 spring保证事务的一致性_事务_08

自身调用

只有在外部调用事务才会生效

  1. 一个普通方法调用一个事务方法
  2. 一个事务方法调用另一个事务方法
  • spring是通过代理来管理事务的,同类调用没有走代理

解决方式就是改为外部调用,拆成两个Service

spring中如何保证ID的一致性 spring保证事务的一致性_spring_09

如果非要在内部调用,可以使用代理对象去调用

spring中如何保证ID的一致性 spring保证事务的一致性_数据_10