大事務引發的問題:死鎖,鎖等待,復原時間長 接口逾時,資料庫主從延遲,并發情況下資料庫連接配接池被打滿
1.@Transactional注解是通過Spring的AOP起作用的,但是如果使用不當,事務功能可能會失效。
2.@Transactional注解一般加在某個業務方法上,會導緻整個業務方法都在這個事務中,粒度太大,不好控制事務範圍。
文章目錄
- 1.少于@Transactional注解
- 2.将查詢(select)方法放到事務外
- 2.1 新加上一個service方法
- 2.2 在該Service類中注入自己
- 2.3 在該Service類中使用AopContext.currentProxy()擷取代理對象
- 3.事務中避免遠端調用
- 4.事務中避免一次性處理太多資料
- 5.非事務處理
- 6.異步處理
1.少于@Transactional注解
多用程式設計式事務
@Autowired
private TransactionTemplate transactionTemplate;
...
public void save(final User user) {
transactionTemplate.execute((status) => {
doSameThing...
return Boolean.TRUE;
})
}
2.将查詢(select)方法放到事務外
如果出現大事務,可以将查詢(select)方法放到事務外,也是比較常用的做法,因為一般情況下這類方法是不需要事務的。
@Transactional(rollbackFor=Exception.class)
public void save(User user) {
queryData1();
queryData2();
addData1();
updateData2();
}
可以将queryData1和queryData2兩個查詢方法放在事務外執行,将真正需要事務執行的代碼才放到事務中,比如:addData1和updateData2方法,這樣就能有效的減少事務的粒度。
利用TransactionTemplate 程式設計式事務的方法
@Autowired
private TransactionTemplate transactionTemplate;
...
public void save(final User user) {
queryData1();
queryData2();
transactionTemplate.execute((status) => {
addData1();
updateData2();
return Boolean.TRUE;
})
}
使用@Transactional注解的方式
public void save(User user) {
queryData1();
queryData2();
doSave();
}
@Transactional(rollbackFor=Exception.class)
public void doSave(User user) {
addData1();
updateData2();
}
這樣搞會使得事務失效
2.1 新加上一個service方法
把@Transactional注解加到新Service方法上,把需要事務執行的代碼移到新方法中。具體代碼如下
@Servcie
public class ServiceA {
@Autowired
prvate ServiceB serviceB;
public void save(User user) {
queryData1();
queryData2();
serviceB.doSave(user);
}
}
@Servcie
public class ServiceB {
@Transactional(rollbackFor=Exception.class)
public void doSave(User user) {
addData1();
updateData2();
}
}
2.2 在該Service類中注入自己
@Servcie
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) {
addData1();
updateData2();
}
}
2.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) {
addData1();
updateData2();
}
}
3.事務中避免遠端調用
我們在接口中調用其他系統的接口是不能避免的,由于網絡不穩定,這種遠端調的響應時間可能比較長,如果遠端調用的代碼放在某個事物中,這個事物就可能是大事務。當然,遠端調用不僅僅是指調用接口,還有包括:發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;
})
}
遠端調用的代碼不放在事務中如何保證資料一緻性呢?這就需要建立:重試+補償機制,達到資料最終一緻性了。
4.事務中避免一次性處理太多資料
如果一個事務中需要處理的資料太多,也會造成大事務問題。比如為了操作友善,你可能會一次批量更新1000條資料,這樣會導緻大量資料鎖等待,特别在高并發的系統中問題尤為明顯。
解決辦法是分頁處理,1000條資料,分50頁,一次隻處理20條資料,這樣可以大大減少大事務的出現。
5.非事務處理
在使用事務之前,我們都應該思考一下,是不是所有的資料庫操作都需要在事務中執行?
@Autowired
private TransactionTemplate transactionTemplate;
...
public void save(final User user) {
transactionTemplate.execute((status) => {
addData();
addLog();
updateCount();
return Boolean.TRUE;
})
}
上面的代碼addLog和updateCount可以不在事務中處理的。因為記錄檔和統計數量這種業務允許少量資料不一緻的情況。
@Autowired
private TransactionTemplate transactionTemplate;
...
public void save(final User user) {
transactionTemplate.execute((status) => {
addData();
return Boolean.TRUE;
})
addLog();
updateCount();
}
當然大事務中要鑒别出哪些方法可以非事務執行,其實沒那麼容易,需要對整個業務梳理一遍,才能找出最合理的答案。
6.異步處理
還有一點也非常重要,是不是事務中的所有方法都需要同步執行?我們都知道,方法同步執行需要等待方法傳回,如果一個事務中同步執行的方法太多了,勢必會造成等待時間過長,出現大事務問題。
@Autowired
private TransactionTemplate transactionTemplate;
...
public void save(final User user) {
transactionTemplate.execute((status) => {
order();
delivery();
return Boolean.TRUE;
})
}
order方法用于下單,delivery方法用于發貨,是不是下單後就一定要馬上發貨呢?
@Autowired
private TransactionTemplate transactionTemplate;
...
public void save(final User user) {
transactionTemplate.execute((status) => {
order();
return Boolean.TRUE;
})
sendMq();
}