Spring非事務方法使用事務的性能問題及使用建議
一、現象
弱網環境下通過@Autowired注入service獲取數據,以下兩種形式性能差距巨大(均無數據庫操作)。
- 代碼生成的service
繼承了CrudServiceImpl的Service
public class ChannelConfigServiceImpl extends CrudServiceImpl<ChannelConfigDao, ChannelConfigDTO> implements ChannelConfigService {
public List<SmsTemplateConfigDTO> findSmsChannelTemplateInfo(ChannelTemplateConfigDTO configDTO) {
return null;
}
}
循環10,000次調用耗時26秒(數據庫在遠程)。
- 移除繼承的service
簡單Service
public class ChannelConfigServiceImpl implements ChannelConfigService {
public List<SmsTemplateConfigDTO> findSmsChannelTemplateInfo(ChannelTemplateConfigDTO configDTO) {
return null;
}
}
循環10,000次調用耗時45毫秒。
二、跟蹤
機器配置
CPU:I7-9750H(12c)
內存:16G
調用情況
自定義如下線程池,充分利用CPU多核性能,共執行10,000次調用。
自定義線程池
private static final ThreadPoolExecutor THREAD_POOL = new ThreadPoolExecutor(
Runtime.getRuntime().availableProcessors(),
Runtime.getRuntime().availableProcessors(),
1000L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100000),
r -> new Thread(r, "sms_pool_" + r.hashCode()));
執行過程中的異常情況
- CPU異常,併發下佔用率極低
- 網卡流量異常,本地調用不應該產生網卡流量
- 查看GC未見異常
- 線程皆爲RUNNING狀態,未發生等待和阻塞,說明線程一直在執行邏輯
- 根據以上情況懷疑線程存在遠程調用,dump線程查看線程狀態,發現大量線程在等待socket返回mysql數據
- 根據線程堆棧定位到sql執行位置com.mysql.jdbc.ConnectionImpl.execSQL函數,斷點觀察此方法實際入參和執行的sql存在以下三種
- SET autocommit=1
- SET autocommit=0
- commit
- 推測執行了大量事務提交。
- 回到業務代碼查看CrudServiceImpl類發現class定義上存在@Transactional註解。
三、結論
在class上定義@Transactional註解會使派生類的所有方法自動增加事務,即使方法沒有數據庫操作也會增加事務提交動作,在特定場景下會嚴重降低系統性能。
四、解決辦法
臨時解決辦法:可在不需要事務的方法上增加(已驗證)
@Transactional( propagation = Propagation. NEVER)
解決辦法:移除class上的@Transactional註解,以業務爲準在有需要的方法上增加事務
五、附錄
spring事務的7種傳播特性
1 REQUIRED 如果存在一個事務,則支持當前事務。如果沒有事務則開啓一個新的事務。
2 SUPPORTS 如果存在一個事務,支持當前事務。如果沒有事務,則非事務的執行
3 MANDATORY 如果已經存在一個事務,支持當前事務。如果沒有一個活動的事務,則拋出異常。
4 NESTED 如果一個活動的事務存在,則運行在一個嵌套的事務中。如果沒有活動事務,則按REQUIRED屬性執行
5 NEVER 總是非事務地執行,如果存在一個活動事務,則拋出異常
6 REQUIRES_NEW 總是開啓一個新的事務。如果一個事務已經存在,則將這個已經存在的事務掛起
7 NOT_SUPPORTED 總是非事務地執行,並掛起任何存在的事務