Skip to the content.

[TOC]

Spring之事务

一、事务的那些概念

1、事务执行正确四要素

ACID

2、事务执行存在问题及隔离级别

​ 事务在执行过程中,以下四种情况是不可避免的问题。

为了处理这几种问题,SQL定义了下面的4个等级的事务隔离级别:

二、Spring之事务

1、事务传播性

Tips: PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 有什么区别呢?

PROPAGATION_REQUIRES_NEW:A的事务和B的事务是完全独立的,当执行到B时,B发现A有事务,则挂起,等B执行完(无论是commit还是rollback),A继续执行。 PROPAGATION_NESTED: 不会创建新的事务,当发现A有事务后,B作为A的子事务(新建一个保存点,但不产生新的事务),当B执行失败,B会rollback(回退到保存点,不会产生),并抛出异常到A,A发现B异常了,可以选择回滚,或者捕捉异常不会滚A的操作,继续执行。

场景举例:

针对PROPAGATION_NESTED的特性(不产生新的事务),在下面这个场景下,很好的提现了其优势。我们有3个Service,目的:ServiceB和ServiceC两者优先B执行,B失败了执行C,且B的数据要求回滚掉,希望尽可能少开事务。

ServiceA:

@Transactional(rollbackFor = RuntimeException.class)
void methodA() {  
    try {  
        ServiceB.methodB();
    } catch (SomeException) {  
        // 执行其他业务  
        ServiceC.methodC()
    }  
}

ServiceB:

@Transactional(propagation = Propagation.NESTED, rollbackFor = RuntimeException.class)
void methodB() {  
   // ...
}  

如上设计,可以在B异常后Rollback掉B的脏数据,继续执行C,TryCatch起到一个分支执行的作用。

2、是否只读

如果将事务设置为只读,表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务。

3、事务超时

事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束。在 TransactionDefinition 中以 int 的值来表示超时时间,默认值是-1,其单位是秒。

3、回滚规则

回滚规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚。

结合数据库思考一些场景

隔离级别

代码如下:

  class UserService {
    private UserDAO userDao;
    private UserManager userManager;
  
    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, rollbackFor = RuntimeException.class)
    public void doSomething(String userMobile) {
      UserEntity userEntity = userDao.selectByUserMobile(userMobile);
      logger.info("isExists:{}", userEntity != null);
      Long userId = userManager.insertByUserMobile(userMobile);
      userEntity = userDao.selectByUserMobile(userMobile);
      logger.info("isExists:{}", userEntity != null);
      userEntity = userDao.selectByPrimaryKey(userId);
      logger.info("isExists:{}", userEntity != null);
    }
  }
  
  class UserManager {
    private UserDAO userDao;

    @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.DEFAULT, rollbackFor = RuntimeException.class)
    public Long insertByUserMobile(String userMobile) {
      UserEntity userEntity = new UserEntity();
      userEntity.setUserMobile(userMobile);
      userDao.insert(userEntity);
      return userEntity.getId();
    }
	
  }

当使用Mysql数据库,配置RR(Repeatable Read,可重复读)隔离级别,使用Innodb,猜猜结果是什么,如果是Oracle(Oracle仅支持Read Committed、Serializable、Read-Only,默认是RC)呢?

Oracle数据一致性参照官方文档:Data Consistency Oracle事务隔离级别参照官方文档: Oracle Database Transaction Isolation Levels

Mysql、隔离级别RR、Innodb:

isExists:false
isExists:false
isExists:false

Oracle、隔离级别RC:

isExists:false
isExists:false
isExists:false

如果代码在稍微调整一下呢?

代码如下:

  class UserService {
    private UserDAO userDao;
    private UserManager userManager;

    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, rollbackFor = RuntimeException.class)
    public void doSomething(String userMobile) {
      /// UserEntity userEntity = userDao.selectByUserMobile(userMobile);
      /// logger.info("isExists:{}", userEntity != null);
      Long userId = userManager.insertByUserMobile(userMobile);
      UserEntity userEntity = userDao.selectByUserMobile(userMobile);
      logger.info("isExists:{}", userEntity != null);
      userEntity = userDao.selectByPrimaryKey(userId);
      logger.info("isExists:{}", userEntity != null);
    }
  }
  
  class UserManager {
    private UserDAO userDao;

    @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.DEFAULT, rollbackFor = RuntimeException.class)
    public Long insertByUserMobile(String userMobile) {
      UserEntity userEntity = new UserEntity();
      userEntity.setUserMobile(userMobile);
      userDao.insert(userEntity);
      return userEntity.getId();
    }
	
  }

Mysql、隔离级别RR、Innodb:

isExists:true
isExists:true

Oracle、隔离级别RC:

isExists:true
isExists:true

总结

SQL标准规范说明了,RR、RC的要求,即RR要求一个事务里,多次读结果是一样的(可能会出现幻读),RC仅要求读的内容是别的事务已提交的内容。

MySql针对RR的实现符合SQL标准规范定义;

Oracle的RC在SQL标准规范定义之外,还多做了RR的要求,这一点在Oracle官方文档中有说明(官方文档),Oracle关于RC做了如下说明:

In the read committed isolation level, every query executed by a transaction sees only data committed before the query—not the transaction—began. 事务执行的每个查询只看到在查询开始之前提交的数据,而不是事务开始。

故这么解释,可以搞得清楚结果,但是往深了去:针对Mysql可以延深到RR级别下,InnoDB,如何实现的,主要会涉及到MVCC、Read View等。具体详细内容,见我的这篇MVCC