水文:Transaction rolled back because it has been marked as rollback-only

145 阅读5分钟

今天测试环境同事写测试代码的时候,出现了一个Transaction rolled back because it has been marked as rollback-only,还发了组内的告警邮件

image.png

过多的信息可不敢截取啊,展示其中的关键信息就好。

辣么问题来了,这个问题是肿么肥四呢?

稍加思考,立马恍然大悟!

为了重现这个异常,我搭建了一个小小的demo:

@Service 
public class TestService { 
    @Autowired 
    private StudentMapper studentMapper; 
    
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class) 
    public void test(){ 
        studentMapper.get(1L); 
        throw new RuntimeException(); 
    } 
}
@Service 
public class StudentService { 
    @Autowired 
    private StudentMapper studentMapper; 
    @Autowired private TestService testService;
    
    @Transactional(propagation = Propagation.REQUIRED, readOnly = true, noRollbackFor = Exception.class) 
    public Student get(Long id) { 
        Student student = studentMapper.get(id); 
        testService.test(); 
        return student; 
    } 
}
@RequestMapping("/student") 
@RestController 
public class StudentController { 

    @Autowired
    private StudentService studentService;

    @RequestMapping("one") 
    public Student save(@RequestParam("id") Long id) { 
    return studentService.get(id); 
    } 
}

请求一下,果然不出山人所料 image.png 让我们来分析一波

首先 test()方法上面加的是一个正常的读写事务,propagation = Propagation.REQUIRED

其次StudentService.get()加的是一个只读的事务,propagation = Propagation.REQUIRED,并且调用了 test()

辣么,推理可得test()与StudentService.get()是公用的同一个事务,然后test()方法内抛了一个异常,所以test()执行完之后,事务会被标记为回滚

但是!

StudentService.get()说我是个只读的啊,我不想要回滚,谁也不能勉强我,所以我要正常提交事务

这样问题就来了啊,事务已经被标记为回滚了,这个时候要去提交,Spring说这我怎么办啊,很蓝的啦,只能抛一个异常这样子的啦;

最终就是我们看到的 Transaction rolled back because it has been marked as rollback-only

这个问题的本质是什么:是serviceA调用了serviceB,两者在同一个事务,B要回滚A要commit,就会出现这样的问题;

所以StudentService.get()换一种写法也可以实现同样的效果

@Transactional(propagation = Propagation.REQUIRED) 
public Student get(Long id) { 
Student student = studentMapper.get(id); 
    try{ 
        testService.test(); 
    }catch (Exception exception){ } 
    return student; 
}

源码分析:

通过方法断点,我们可以很快地定位到,事务执行的切面代码,关键代码展示如下

protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation) throws Throwable { 
    TransactionAttributeSource tas = getTransactionAttributeSource(); 
    final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null); 
    //获取事务管理器 
    final PlatformTransactionManager tm = determineTransactionManager(txAttr); 
    //切点定义解析,也就是目标方法,如test()与StudentService.get() 
    final String joinpointIdentification = methodIdentification(method, targetClass, txAttr); 
    if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) { 
        // 创建事务 
        TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); Object retVal = null; 
        try { 
            //执行目标方法 
            retVal = invocation.proceedWithInvocation(); 
        } catch (Throwable ex) { 
            // 目标方法异常后的事务处理方式 
            completeTransactionAfterThrowing(txInfo, ex); 
            throw ex; 
        } finally { 
            cleanupTransactionInfo(txInfo); 
        } 
        // 目标方法正常执行后的事务处理方式 
        commitTransactionAfterReturning(txInfo); return retVal; 
    } 
}

先看事务的创建逻辑

protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm, @Nullable TransactionAttribute txAttr, final String joinpointIdentification) { 
    // TransactionAttribute命名,不重要 
    if (txAttr != null && txAttr.getName() == null) { 
        txAttr = new DelegatingTransactionAttribute(txAttr) { 
            @Override 
            public String getName() { 
                return joinpointIdentification; 
            } 
        }; 
    } 
    TransactionStatus status = null; 
    if (txAttr != null) { 
        if (tm != null) { 
            //获取事务 
            status = tm.getTransaction(txAttr); 
        } 
    } 
    return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status); 
}

获取事务的关键代码:

@Override public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException { 
    Object transaction = doGetTransaction(); 
    if (isExistingTransaction(transaction)) { 
        // 如果当前已经存在一个事务的处理方式 
        return handleExistingTransaction(definition, transaction, debugEnabled); 
    } 
    //如果没有事务,并且PROPAGATION是下列几种PROPAGATION_REQUIRED PROPAGATION_REQUIRES_NEW PROPAGATION_NESTED 
    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED || definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW || definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) { 
        SuspendedResourcesHolder suspendedResources = suspend(null); 
        try { 
            boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); 
            // 初始化事务的状态 
            DefaultTransactionStatus status = newTransactionStatus( definition, transaction, true, newSynchronization, debugEnabled, suspendedResources); 
            // 开启事务 
            doBegin(transaction, definition); 
            // 不知道,暂时不管 
            prepareSynchronization(status, definition); return status; 
        } catch (RuntimeException | Error ex) { 
            resume(null, suspendedResources); 
            throw ex; 
        } 
    } else { 
        boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
        return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null); 
    } 
}

通过上面代码可以看到,StudentService.get()方法执行的时候,当前是没有事务的,所以执行代码是 初始化事务状态——开启事务等操作;对于test()方法执行的时候已经有一个事务了,所以需要执行handleExistingTransaction()方法,主要逻辑如下:

private TransactionStatus handleExistingTransaction( TransactionDefinition definition, Object transaction, boolean debugEnabled) throws TransactionException { 
    ... 
    if (isValidateExistingTransaction()) { 
    // 隔离级别是否一致 
        if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) { 
            Integer currentIsolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel(); 
            if (currentIsolationLevel == null || currentIsolationLevel != definition.getIsolationLevel()) { 
                Constants isoConstants = DefaultTransactionDefinition.constants; 
                throw new IllegalTransactionStateException("Participating transaction with definition [" + definition + "] specifies isolation level which is incompatible with existing transaction: " + (currentIsolationLevel != null ? isoConstants.toCode(currentIsolationLevel, DefaultTransactionDefinition.PREFIX_ISOLATION) : "(unknown)")); 
            } 
        } 
        if (!definition.isReadOnly()) { 
            if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) { 
                throw new IllegalTransactionStateException("Participating transaction with definition [" + definition + "] is not marked as read-only but existing transaction is"); 
            } 
        } 
    } 
    boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); 
    // 复用老事务的连接 
    return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null); 
}

经过上述代码可以看到,当事务嵌套的时候,如果被嵌套的事务propagation = Propagation.REQUIRED,那么就会复用外层的事务,也就是我们说的公用了同一个事务连接;

那么当test()发生异常的时候执行completeTransactionAfterThrowing()逻辑,主要代码如下

protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) { 
    if (txInfo != null && txInfo.getTransactionStatus() != null) { 
        // 如果发生的异常,在回滚的异常范围内,那么走事务回滚逻辑 
        if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) { 
            try { 
                // 标记事务状态为回滚/回滚事务
                txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus()); 
            } catch (TransactionSystemException ex2) { 
                throw ex2; 
            } catch (RuntimeException | Error ex2) { 
                throw ex2; 
            } 
        } else { 
        ... 
        } 
    } 
}

其中对于txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus()); 最终会判断如果当前事务方法是被嵌套的事务,并且共用了一个大事务,那么会把这个事务标记为rollbackOnly,代码为

    txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());

而对于StudentService.get()方法(以try-catch方式为例),异常被捕获,整个方法正常执行,所以会进入到commitTransactionAfterReturning(txInfo);

最终追踪到

@Override public final void commit(TransactionStatus status) throws TransactionException { 
    DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status; 
    if (defStatus.isLocalRollbackOnly()) { 
        processRollback(defStatus, false); 
        return; 
    } 
    if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) { 
        //如果发现事务被标记为 rollbackOnly了,那么不会提交事务,反而执行回滚逻辑,并且,标记这个回滚是超出预期的 
        processRollback(defStatus, true); 
        return; 
    } 
    //提交事务
    processCommit(defStatus); 
}

最终对于超出预期的事务回滚,在回滚逻辑执行完成后,抛出异常

if (unexpectedRollback) { 
    throw new UnexpectedRollbackException( "Transaction rolled back because it has been marked as rollback-only"); 
}

以上就是整个代码追踪的全部过程啦