大事務問題一般要如何解決?

大事務引發的問題

從上圖可以看出如果系統中出現大事務時,問題還不小,所以我們在實際項目開發中應該儘量避免大事務的情況。如果我們已有系統中存在大事務問題,該如何解決呢?

解決辦法

少用@Transactional註解

部分代碼如下:

@Transactional(rollbackFor=Exception.class)
   public void save(User user) {
         doSameThing()...
   }

然而,我要說的第一條是:少用@Transactional註解。

爲什麼?

  1. 我們知道@Transactional註解是通過spring的aop起作用的,但是如果使用不當,事務功能可能會失效。如果恰巧你經驗不足,這種問題不太好排查。至於事務哪些情況下會失效
  2. @Transactional註解一般加在某個業務方法上,會導致整個業務方法都在同一個事務中,粒度太粗,不好控制事務範圍,是出現大事務問題的最常見的原因。

 

可以使用編程式事務,在spring項目中使用TransactionTemplate類的對象,手動執行事務。(推薦)

部分代碼如下:

 

@Autowired
   private TransactionTemplate transactionTemplate;
   
   ...
       
   public void save(final User user) {
       transactionTemplate.execute(new TransactionCallback(){
		@Override
		public Boolean doInTransaction(TransactionStatus status) {
 		 	doSameThing()...
			return Boolean.TRUE;
		  }
	   });  
  }

從上面的代碼中可以看出,使用TransactionTemplate的編程式事務功能自己靈活控制事務的範圍,是避免大事務問題的首選辦法。

將查詢(select)方法放到事務外

如果出現大事務,可以將查詢(select)方法放到事務外,也是比較常用的做法,因爲一般情況下這類方法是不需要事務的。

比如出現如下代碼(不推薦使用)

@Transactional(rollbackFor=Exception.class)
   public void save(User user) {
         queryData1();
         queryData2();
         insertA(user);
         updateB(user);
   }


可以將queryData1和queryData2兩個查詢方法放在事務外執行,將真正需要事務執行的代碼才放到事務中,比如:addData1和updateData2方法,這樣就能有效的減少事務的粒度。

改寫如下(推薦使用):

@Autowired
   private TransactionTemplate transactionTemplate;
   
   ...
   
   public void save(final User user) {
         queryData1();
         queryData2();
      transactionTemplate.execute(new TransactionCallback(){
		@Override
		public Boolean doInTransaction(TransactionStatus status) {
 		    addData1();
            updateData2();
			return Boolean.TRUE;
		  }
	   }); 
   }

但是如果你實在還是想用@Transactional註解,該怎麼拆分呢?


這個寫法是否有問題?

public void save(User user) {
         queryData1();
         queryData2();
         doSave();
    }
   
    @Transactional(rollbackFor=Exception.class)
    public void doSave(User user) {
         insertA(user);
         updateB(user);
    }

 

這個例子是非常經典的錯誤,這種直接方法調用的做法事務不會生效,給正在坑中的朋友提個醒。因爲@Transactional註解的聲明式事務是通過spring aop起作用的,而spring aop需要生成代理對象,直接方法調用使用的還是原始對象,所以事務不會生效。

有沒有辦法解決這個問題呢?


1.新加一個Service方法

@Service
  public class ServiceA {
     @Autowired
     prvate ServiceB serviceB;
  
     public void save(User user) {
           queryData1();
           queryData2();
           serviceB.doSave(user);
     }
   }
   
   @Service
   public class ServiceB {
   
      @Transactional(rollbackFor=Exception.class)
      public void doSave(User user) {
         insertA(user);
         updateB(user);
      }
   
   }

2.在該Service類中注入自己

可以使用這樣(可以使用):

@Service
  public class ServiceA {
     @Autowired
     prvate ServiceA serviceA;
  
     public void save(User user) {
           queryData1();
           queryData2();
           serviceA.doSave(user);
     }
     
     @Transactional(rollbackFor=Exception.class)
     public void doSave(User user) {
         insertA(user);
         updateB(user);
      }
   }

 

3.在該Service類中使用AopContext.currentProxy()獲取代理對象

還可以通過在該Service類中使用AOPProxy獲取代理對象,實現相同的功能。具體代碼如下(可以使用):

@Servcie
  public class ServiceA {
  
     public void save(User user) {
           queryData1();
           queryData2();
           ((ServiceA)AopContext.currentProxy()).doSave(user);
     }
     
     @Transactional(rollbackFor=Exception.class)
     public void doSave(User user) {
         insertA(user);
         updateB(user);
      }
   }

 

事務中避免遠程調用

如果遠程調用的代碼放在某個事物中,這個事物就可能是大事務。當然,遠程調用不僅僅是指調用接口,還有包括:發MQ消息,或者連接redis、mongodb保存數據等

(不推薦使用):

@Transactional(rollbackFor=Exception.class)
   public void save(User user) {
         callRemoteApi();
         addData1();
   }


遠程調用的代碼可能耗時較長,切記一定要放在事務之外。(推薦使用):

@Autowired
   private TransactionTemplate transactionTemplate;
   
   ...
   
   public void save(final User user) {
         callRemoteApi();
         transactionTemplate.execute((status) => {
            addData1();
            return Boolean.TRUE;
         })
   }

 

異步處理

(不推薦使用):

@Autowired
   private TransactionTemplate transactionTemplate;
   
   ...
   
   public void save(final User user) {
         transactionTemplate.execute((status) => {
            order();
            delivery();
            return Boolean.TRUE;
         })
   }


答案是否定的。

這裏發貨功能其實可以走mq異步處理邏輯。

(推薦使用):

@Autowired
   private TransactionTemplate transactionTemplate;
   
   ...
   
   public void save(final User user) {
         transactionTemplate.execute((status) => {
            order();
            return Boolean.TRUE;
         })
         sendMq();
   }

 

總結

本人結合自己實際的工作經驗分享了處理大事務的6種辦法:

  1. 少用@Transactional註解,減少事務的粒度,推薦使用TransactionTemplate
  2. 將查詢(select)方法放到事務外
  1. 事務中避免遠程調用
  2. 事務中避免一次性處理太多數據
  1. 非事務執行
  2. 異步處理
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章