線上的系統中,使用的是Spring+Mybatis+Mysql搭建的架構,由于客戶需要,最近一直在對性能提升部分進行考慮,主要是涉及Mysql的一些重要參數的配置學習,以及Spring事務管理機制的學習,因為通過觀察伺服器日志,發現在這兩部分的時候耗時比較嚴重,特别是進行mysql事務送出的時候,項目源碼中使用了Spring的聲明式事務,即通過@Transactional注解來控制事務的開啟與送出,這兩天看了一些關于Spring Transaction事務的一些文章,也debug了源碼,總算有點心得和疑問,這裡簡單的整理一下。
在Spring的配置檔案中,我們使用了"org.springframework.jdbc.datasource.DataSourceTransactionManager"對事務進行管理,翻開DataSourceTransactionManager的源碼,我們看到DataSourceTransactionManager繼承了AbstractPlatformTransactionManager(抽象的事務管理器),DataSourceTransactionManager重寫了其中的一些方法,具體每個方法的作用,限于篇幅,本文不再贅述,這裡《Spring技術内幕》學習筆記16——Spring具體事務處理器的實作有詳細的介紹。
在現有的項目中,我們在public方法上面使用了@Transactional注解,當有線程調用此方法時,Spring會首先掃描到@Transactional注解,進入DataSourceTransactionManager繼承自AbstractPlatformTransactionManager的getTransaction()方法,在getTransaction()方法内部,會調用doGetTransaction()方法,@Transactional的注解中,存在一個事務傳播行為的概念,即propagation參數,預設等于PROPAGATION_REQUIRED,表示如果目前沒有事務,就建立一個事務,如果存在一個事務,方法塊将使用這個事務,具體其他參數的意義請看下圖:

在getTransaction方法中,DataSourceTransactionManager重寫了isExistingTransaction()方法,用于判斷目前是否存在事務,以下是其的源碼:
@Override
protected boolean isExistingTransaction(Object transaction) {
logger.debug(Thread.currentThread().getName() + ">>>" + "DataSourceTransactionManager.isExistingTransaction()");
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
return (txObject.getConnectionHolder() != null && txObject.getConnectionHolder().isTransactionActive());
}
但是,這幾天我對源碼進行調試的過程中,發現多線程并發的時候,isExistingTransaction方法總是傳回的false,即ConnectionHolder總是為空,這是遇到的第一個疑問點,目前還沒有弄清楚。由于源碼判斷目前不存在事務,是以總是會Creating new transaction,即建立一個事務。建立事務之後,會執行重寫的doBegin()方法,在doBegin方法中,首先通過下面的代碼判斷了ConnectionHolder是否為空,如下:
if (txObject.getConnectionHolder() == null
|| txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
Connection newCon = this.dataSource.getConnection();
if (logger.isDebugEnabled()) {
logger.debug(Thread.currentThread().getName() + ">>>" + "Acquired Connection [" + newCon
+ "] for JDBC transaction");
}
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
這裡會從目前配置的資料源中擷取一個連接配接,然後設定相應的ConnectionHolder,接下來是關鍵的一步,也是存在的第二個問題點,拿到connection後,會首先判斷connection的autoCommit屬性是否為true,之前工作中在使用原始JDBC的時候,當進行事務的控制時,我們總是會首先設定autoCommit為false,禁止事務自動送出,然後commit送出事務,最後設定autoCommit為true。Spring Transaction也是這樣進行管理的,但是問題來了, 先看源碼:
if (con.getAutoCommit()) {
txObject.setMustRestoreAutoCommit(true);
if (logger.isDebugEnabled()) {
logger.debug(Thread.currentThread().getName() + ">>>" + "Switching JDBC Connection [" + con
+ "] to manual commit");
}
con.setAutoCommit(false);
logger.debug(Thread.currentThread().getName() + ">>>" + "Set Transaction AutoCommit False [" + con
+ "]");
}
這裡有個Swithching JDBC Connection的操作,就是為了設定autoCommit為false,但是在這幾天進行多線程并發測試的時候,發現這一部分的代碼耗時非常嚴重,這是目前還不清楚的第二點。
接下來,開始涉及MyBatis SqlSession部分的一些機制,關于MyBatis sqlSession的一點整理,SqlSession主要是MyBatis定義的用于進行持久化操作的對象,對connection進行了包裝。在上面Spring Transaction的機制中,我們擷取到了connection,之後會首先調用SqlSessionUtils裡面的getSession方法,判斷目前的sqlSessionHolder以及是否存在事務鎖定。
如果不存在sessionHolder或resources未被事務鎖定,就會Creating a new SqlSession,然後為sqlSession注冊事務資源,即Registering transaction synchronization for SqlSession。之後把connection交給Spring管理;如果存在可用的sessionHolder并且被事務鎖定,就會從目前的事務中拿到SqlSession,即Fetched SqlSession from current transaction。當代碼中涉及資料庫的操作時,就可以從資料源中擷取到相應的connection,即Using Connection,這時候就有了第三個問題,在進行debug源碼測試的時候,發現using connection這一步的耗時也比較嚴重。
在使用完連接配接後,SqlSessionUtils會釋放事務的SqlSession,即Releasing transactional SqlSession。接下來,就可以準備執行事務的送出了,即Initiating transaction commit,這裡會調用DataSourceTransactionManager中重寫的doCommit()方法,在其中進行事務的送出操作,即Committing JDBC transaction on Connection,為目前的連接配接送出事務。這裡又有了第四個問題,進行事務送出的操作,在進行多線程并發測試的時候,發現耗時非常嚴重。也嘗試過修改連接配接池或者mysql的配置,問題總是得不到解決。
在事務送出之後,又回到SqlSessionUtils執行其中的afterCompletion方法,進行MyBatis SqlSession層面的處理。SqlSessionUtils會首先Transaction synchronization committing SqlSession,送出SqlSession,然後關閉SqlSession,即Transaction synchronization closing SqlSession,在MyBatis層面處理完成後,會再次回到DataSourceTransactionManager,執行其中的doCleanupAfterCompletion方法,釋放一些資源,包括: Releasing JDBC Connection 釋放JDBC連接配接,Returning JDBC Connection to DataSource 将連接配接放回到資料源。
至此,對于Spring Transaction + MyBatis SqlSession事務管理機制,已經做了大緻的研究學習,閱讀了其中涉及的源碼,以及參閱了一些網上的部落格,有了一些自己的認識,很近自己的了解整理出了一個簡單的時序圖,其中也有一些疑問,最後做一下記錄。
主要是包括四個疑問點:
問題1:在現有架構下isExistingTransaction傳回false,即ConnectionHolder為空
問題2:切換JDBC connection屬性,主要是setAutoCommit為false,禁止事務自動送出,耗時不穩定,有時很慢
問題3: 使用資料源中的connection時,connection擷取耗時有時很慢
問題4: Spring Transaction再進行事務送出時commit耗時嚴重
這裡根據自己的了解整理出了一個簡單的時序圖,共享給大家,其中一定涉及一些不合理甚至了解錯誤的地方,希望大家不吝賜教。