1. 引言

我们常听到事务隔离级别这个概念,而又分spring事务隔离级别和数据库事务隔离级别,那么它们分别是什么?并且有什么关系呢?

2. spring隔离级别

常量

解释

ISOLATION_DEFAULT

这是个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与 JDBC 的隔离级别相对应。

ISOLATION_READ_UNCOMMITTED

事务中的修改,即使没有提交,其他事务也可以看得到,会导致“脏读”、“幻读”和“不可重复读取”

ISOLATION_READ_COMMITTED

大多数主流数据库的默认事务等级,避免了“脏读取”,但不能避免“幻读”和“不可重复读取”,SQL Server和Oracle数据库默认的隔离级别

ISOLATION_REPEATABLE_READ

避免了“脏读取”和“不可重复读取”的情况,但不能避免“幻读” ,Mysql数据库默认的隔离级别

ISOLATION_SERIALIZABLE

事务被处理为串行化顺序执行,代价最大,很少用

spring事务

  • spring事务实际上是使用spring的一大特性AOP拦截注解的方法,动态代理处理异常情况;
  • spring事务只有捕获到异常才会终止或回滚,如果你在程序中try/catch后自己处理异常而没有throw,那么事务将不会起作用(终止或回滚);
  • spring事务会捕获所有的异常,但只会回滚数据库相关的操作,并且只有在声明了rollbackForClassName="Exception"之类的配置才会回滚;
  • spring事务会回滚同一事务中的所有数据库操作,本质上是回滚同一数据库连接上的数据库操作;

使用spring难免要用到spring的事务管理,要用事务管理又会很自然的选择声明式的事务管理;

spring声明式事务管理
spring声明式事务管理默认对非检查型异常运行时异常进行事务回滚,而对检查型异常则不进行回滚操作。

那么什么是检查型异常什么又是非检查型异常呢?
最简单的判断点有两个:

  • 继承自runtimeexception或error的是非检查型异常,其余的是检查异常
  • 对非检查型类异常可以不用捕获,而检查型异常则必须用try{}catch{}语句块进行处理或把异常交给上级方法处理,总之就是必须写代码处理它。所以必须在service捕获异常,然后再次抛出,这样事务方才起效
    即异常被try{}catch{}了,事务就不回滚了,如果想让事务回滚必须再往外抛try{}catch{throw Exception}。
@Transactional(rollbackFor=Exception.class)   //默认只有运行时异常才事务回滚

@Transactional(notRollbackFor=RunTimeException.class)  //非运行时异常才事务回滚

@Transactional(propagation=Propagation.NOT_SUPPORTED)  //不需要事务管理的(只查询的)方法

3. 数据库隔离级别

隔离级别

隔离级别的值

导致的问题

Read-Uncommitted

0

导致脏读

Read-Committed

1

避免脏读,允许不可重复读和幻读

Repeatable-Read

2

避免脏读,不可重复读,允许幻读

Serializable

3

串行化读,事务只能一个一个执行,避免了脏读、不可重复读、幻读。执行效率慢,使用时慎重

  1. 为什么会出现“脏读”?
    因为没有“select”操作没有规矩。
  2. 为什么会出现“不可重复读”?
    因为“update”操作没有规矩。
  3. 为什么会出现“幻读”?
    因为“insert”和“delete”操作没有规矩。

4. spring隔离级别&数据库隔离级别的关系

spring默认使用的隔离级别是ISOLATION_DEFAULT,随数据库默认的隔离级别;而spring事务本质上使用数据库事务,而数据库事务本质上使用数据库锁,所以spring事务本质上使用数据库锁,开启spring事务意味着使用数据库锁;

5. 怎么解决避免不了幻读的问题?

  • 既然主流数据库在隔离级别上都避免不了幻读,那么这个问题是怎么解决的呢?

通过MVCC(multi version concurrent control)多版本并发控制机制来控制的,mysql数据库上其实每条上面都还有两个冗余字段,一个是行的创建版本,一个是行的删除(过期)版本。具体的版本号(trx_id)存在 information_schema.INNODB_TRX 表中。版本号(trx_id)随着每次事务的开启自增。
原理 :将历史数据存一份快照,所以其他事务增加与删除数据,对于当前事务来说是不可见的。