发现项目有一个老方法上使用的事物注解是@Transactional(propagation = Propagation.REQUIRES_NEW)
,然后在外层方法使用@Transactional
就会导致死锁问题。
仔细找了一下原因,spring事物的Propagation.REQUIRES_NEW
传播属性会新起一个事物,那么再加上外层方法的@Transactional
就会同时开启两个事物。两个事物同时对mysql的同一行记录加锁就产生的死锁问题。
代码大概是这样的:
@Service
public class OrderServiceImpl {
private OrderDao orderDao; //spring注入
public Order findByOrderNoWithLock(String orderNo) {
//sql语句是 select * from t_order where order_no = ? for update
return orderDao.findByOrderNoWithLock(orderNo);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void payOrder(String orderNo){
Order order = findByOrderNoWithLock(orderNo);
if(order == null) {
throw new OrderException("该订单号不存在");
}
......省略业务逻辑
}
}
@Service
public class TestServiceImpl {
private OrderServiceImpl orderService; //spring注入
@Transactional
public void test(){
orderService.findByOrderNoWithLock("111111");
payOrder("111111");//死锁导致阻塞
......省略业务逻辑
}
}
select ... for update
执行第二次的时候就出现了死锁。
事务一 | 事务二 |
---|---|
begin | |
select * from t_order where order_no = ? for update | begin |
select * from t_order where order_no = ? for update | |
Query OK | |
Lock wait timeout exceeded; try restarting transaction | |
commit; |
Spring的传播属性不光只有Propagation.REQUIRES_NEW
这一种,我们来看一下Propagation
枚举。
public enum Propagation {
/**
* Support a current transaction, create a new one if none exists.
* Analogous to EJB transaction attribute of the same name.
* This is the default setting of a transaction annotation.
*/
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
/**
* Support a current transaction, execute non-transactionally if none exists.
* Analogous to EJB transaction attribute of the same name.
*
Note: For transaction managers with transaction synchronization,
* PROPAGATION_SUPPORTS is slightly different from no transaction at all,
* as it defines a transaction scope that synchronization will apply for.
* As a consequence, the same resources (JDBC Connection, Hibernate Session, etc)
* will be shared for the entire specified scope. Note that this depends on
* the actual synchronization configuration of the transaction manager.
* @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization
*/
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
/**
* Support a current transaction, throw an exception if none exists.
* Analogous to EJB transaction attribute of the same name.
*/
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
/**
* Create a new transaction, and suspend the current transaction if one exists.
* Analogous to the EJB transaction attribute of the same name.
*
NOTE: Actual transaction suspension will not work out-of-the-box
* on all transaction managers. This in particular applies to
* {@link org.springframework.transaction.jta.JtaTransactionManager},
* which requires the {@code javax.transaction.TransactionManager} to be
* made available it to it (which is server-specific in standard Java EE).
* @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager
*/
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
/**
* Execute non-transactionally, suspend the current transaction if one exists.
* Analogous to EJB transaction attribute of the same name.
*
NOTE: Actual transaction suspension will not work out-of-the-box
* on all transaction managers. This in particular applies to
* {@link org.springframework.transaction.jta.JtaTransactionManager},
* which requires the {@code javax.transaction.TransactionManager} to be
* made available it to it (which is server-specific in standard Java EE).
* @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager
*/
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
/**
* Execute non-transactionally, throw an exception if a transaction exists.
* Analogous to EJB transaction attribute of the same name.
*/
NEVER(TransactionDefinition.PROPAGATION_NEVER),
/**
* Execute within a nested transaction if a current transaction exists,
* behave like PROPAGATION_REQUIRED else. There is no analogous feature in EJB.
*
Note: Actual creation of a nested transaction will only work on specific
* transaction managers. Out of the box, this only applies to the JDBC
* DataSourceTransactionManager when working on a JDBC 3.0 driver.
* Some JTA providers might support nested transactions as well.
* @see org.springframework.jdbc.datasource.DataSourceTransactionManager
*/
NESTED(TransactionDefinition.PROPAGATION_NESTED);
}
一共有七种,解释大概是这个样子的:
事务传播属性 | 说明 |
---|---|
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
关于PROPAGATION_REQUIRED属性
PROPAGATION_REQUIRED是spring @Transactional
注解的默认propagation
属性,也是最常用的一个属性。简单的说就是多个方法嵌套,并且上面都用@Transactional(propagation = Propagation.REQUIRED)
那么最终只有最外层的那个方法的事务才会生效。
@Service
class Service1{
@Autowired
private Service2 service2;
@Transactional(propagation = Propagation.REQUIRED)
public void method1(){
service2.method2()
}
}
@Service
class Service2{
@Transactional(propagation = Propagation.REQUIRED)
public void method2(){
method3()
}
@Transactional(propagation = Propagation.REQUIRED)
public void method3(){
...
}
}
以上只有method1()这个方法事物会生效, method2(),method3()这两个任意方法只要报错了就会导致回滚。
关于PROPAGATION_SUPPORTS和PROPAGATION_MANDATORY属性
PROPAGATION_SUPPORTS
和PROPAGATION_MANDATORY
属性很类似,都是本身的事物并不重要,重要的是调用者的事物。
PROPAGATION_SUPPORTS
属性是指调用者有事物就跟着调用者用同一个事务执行,调用者没有事务就以非事务的形式执行。可以理解成这个方法上面@Transactional(propagation = Propagation.SUPPORTS)
注解并不能生效,仅仅只是一个声明。PROPAGATION_MANDATORY
属性是指调用者有事物就跟着调用者用同一个事务执行,调用者没有事务就抛出异常。
@Service
class Service1{
@Autowired
private Service2 service2;
@Transactional(propagation = Propagation.REQUIRED)
public void methodA(){
service2.method2() //正常以同一个事务的形式执行
}
@Transactional(propagation = Propagation.REQUIRED)
public void methodB(){
service2.method3() //正常以同一个事务的形式执行
}
public void methodC(){
service2.method3() //这里会抛出异常
}
}
@Service
class Service2{
@Transactional(propagation = Propagation.SUPPORTS)
public void method2(){
...
}
@Transactional(propagation = Propagation.MANDATORY)
public void method3(){
...
}
}
以上只有methodA()和methodB()两个方法事物会生效, methodC()这个方法执行到service2.method3()这一行的时候就会发生异常。
关于PROPAGATION_REQUIRES_NEW属性
PROPAGATION_REQUIRES_NEW
属性会在调用者执行到这个方法的时候新起一个事务,与调用者的事务互相独立。调用者异常不会影响到这个方法回滚,反之也不会影响调用者回滚。这个属性在文章最开始举过例子,在新起一个事务的同时原事务是挂起状态,原事务并不会提交,假如在原事务和新事务都持有同一个行锁或者表锁的时候就会产生死锁问题。
关于PROPAGATION_NOT_SUPPORTED属性和PROPAGATION_NEVER属性
PROPAGATION_NOT_SUPPORTED
和PROPAGATION_NEVER
属性也很类似,都是不以事务的方式执行。
PROPAGATION_NOT_SUPPORTED
属性在调用其他事务方法的时候会禁用该方法的事务,在被其他事务方法调用的时候会独自以非事务的状态执行。也就是说被@Transactional(propagation = Propagation.NOT_SUPPORTED)
注解的方法无论无何都不会执行事务。PROPAGATION_NEVER
属性在被有事务的调用者调用的时候就会抛出异常。
@Service
class Service1{
@Autowired
private Service2 service2;
@Transactional(propagation = Propagation.NOT_SUPPORTED) //整个方法都不会以事务执行
public void methodA(){
service2.method2() //该方法的事务不会生效
}
@Transactional(propagation = Propagation.REQUIRED)
public void methodB(){
service2.method3() //到这里会被单独以非事务的形式执行,执行完后再以事务的方式执行methodA()方法
}
@Transactional(propagation = Propagation.REQUIRED)
public void methodC(){
service2.method4() //到这里会抛异常
}
}
@Service
class Service2{
@Transactional(propagation = Propagation.REQUIRED)
public void method2(){
...
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void method3(){
...
}
@Transactional(propagation = Propagation.NEVER)
public void method4(){
...
}
}
methodA()整个方法都不会以事务执行,methodB()会以事务执行,但是执行到service2.method3()的时候会被挂起,以非事务的方式执行完service2.method3()再执行methodB()的事务。methodC()方法在执行service2.method4()的时候就会抛异常。
关于PROPAGATION_NESTED属性
PROPAGATION_NESTED
属性与PROPAGATION_REQUIRES_NEW
类似都作用于嵌套事务,但区别是PROPAGATION_REQUIRES_NEW
与调用者的事务是相互独立的,互不影响。而PROPAGATION_NESTED
的事务和他的调用者事务是相依的,调用者成功提交他才会成功提交,调用者回滚他也会回滚。反之如果他的事务是失败的,调用者不一定失败,会把sql回滚到savepoint的状态继续执行。
@Service
class Service1{
@Autowired
private Service2 service2;
@Transactional(propagation = Propagation.NESTED) //会以Propagation.REQUIRED方式执行
public void methodA(){
}
@Transactional(propagation = Propagation.REQUIRED)
public void methodB(){
doSomething1();
//savepoint
service2.method2() //假如失败就会回滚到savepoint执行状态
doSomething2();
}
}
@Service
class Service2{
@Transactional(propagation = Propagation.NESTED)
public void method2(){
...
}
}
执行methodA()方法会和Propagation.REQUIRED
属性一样,执行一个普通的事务方法。执行methodB()方法会看service2.method2() 这个方法事务是否失败,假如成功了会和methodB()的事务一起提交,如果失败了则会回滚到service2.method2()方法执行之前的状态,再继续执行 doSomething2()。