Spring事務對所有方法全局開啓的潛在問題

Spring 事務管理器基於AOP切入方法來實現的,開發人員可以使用XML配置,也可以使用Annotation來標記具體的方法,可能有的開發人員爲了簡單省事,就講AOP切入的方法定義成所有方法,那麼寫代碼就簡單了,這樣做到底有沒有問題呢?肯定有,有興趣的開發者可以一起探討下:

 

應用於DB之間的通信次數增加了幾次,1次?2次?3次?....

增加事務操作後,通信次數自然會變多,通信次數變多導致的直接問題都有那些呢:

  • 通信次數變多,對於系統大併發場景下,應用訪問DB的延遲會倍增,業務延遲變大,同時連接佔用時間變長會導致整體DB連接數增加
  • 對於應用與DB之間延遲較大的場景,會在通信次數上倍增,例如0.02ms的延遲增加1次沒什麼感覺,5ms的延遲增加1次就有點變化了,200ms延遲增加1次就會比較明顯,如果開啓事務增加的延遲不止一次,那麼會被進一步放大

Spring AOP事務處理中,對普通的事務處理步驟爲:

步驟 Spring操作Connection的動作 MySQL JDBC實際操作 增加數據庫通信次數

開啓事務(Begin)

setAutoCommit(false) set autommit=0; 1
執行SQL 多條SQL逐步執行 -- 0

提交或回滾事務

 

commit()/rollback() commit; rollback; 1
恢復連接自動提交狀態 setAutoCommit(true) set autocommit=1 1

也就是會增加3次數據庫通信操作,如果所有的操作都開啓事務,相信業務中大量的數據庫操作都是簡單、單行類操作較多,那麼事務的通信次數可能會達到本身業務的3倍,整體有可能在最壞情況會被放大4倍。例如原本2ms的操作可能會變成8ms,原本100ms可以完成的操作400ms。

而這不併是最壞的情況,有的開發者爲了達到某些業務目標,開啓Spring事務的同時,還開啓了只讀事務(就只讀事務在業務的應用會在其他文章中展開,本文只討論只讀事務會增加的操作):

只讀事務在Spring的XML配置上增加read-only="true"或在Annotation上指定:readOnly = true就可以實現,如果設置了這個參數,事務處理邏輯會變成這樣:

步驟 Spring操作Connection的動作 MySQL JDBC實際操作 增加數據庫通信次數
設置事務只讀(Begin) setReadOnly(true) set session transaction read only; 1

開啓事務(Begin)

setAutoCommit(false) set autommit=0; 1
執行SQL 多條SQL逐步執行 -- 0

提交或回滾事務

 

commit()/rollback() commit; rollback; 1
恢復連接自動提交狀態 setAutoCommit(true) set autocommit=1 1
恢復連接爲讀寫事務 setReadOnly(false) set session transaction read write; 1

此時最壞情況增加延遲放大倍數爲:6倍,也就是對於很多單條SQL的DML語句100ms延遲可能會變成600ms。

這還不是最壞情況,在只讀事務開啓後,需要配合事務隔離級別來完成,可以在Spring的XML上設置isolation來完成,也可以直接在Annotation上加isolation實現如下:

隔離級別 XML取值 Annotation取值(Isolation類) 數據庫標準事務隔離值(Connection類)
默認 DEFAULT ISOLATION_DEFAULT(-1)

--

讀未提交 READ_UNCOMMITTED ISOLATION_READ_UNCOMMITTED(1)

TRANSACTION_READ_UNCOMMITTED

讀已提交 READ_COMMITTED ISOLATION_READ_COMMITTED(2) TRANSACTION_READ_COMMITTED
可重複度 REPEATABLE_READ ISOLATION_REPEATABLE_READ(4) TRANSACTION_REPEATABLE_READ
串行化

SERIALIZABLE

ISOLATION_SERIALIZABLE(8) TRANSACTION_SERIALIZABLE

默認狀態下,Spring不會調用事務隔離級別方法:connection.setTransactionIsolation(int),但如果XML或Anntation配置了事務隔離級別則最少會通過:connection.getTransactionIsolation()獲取當前會話的事務隔離級別,如果與設置的事務隔離級別不一致,則會在執行SQL前後分別調用一次事務隔離級別的設置,如下:

步驟 Spring操作Connection的動作 MySQL JDBC實際操作 增加數據庫通信次數
設置事務只讀(Begin) setReadOnly(true) set session transaction read only; 1
獲取事務隔離級別(Begin) getTransactionIsolation()

MySQL 4.0.3以後版本:

SELECT @@session.tx_isolation

 

更早版本:
SHOW VARIABLES LIKE 'transaction_isolation'

1
設置事務隔離級別(Begin) setTransactionIsolation()

SET SESSION TRANSACTION ISOLATION LEVEL  <隔離級別>

 

隔離級別參考上一個表中:數據庫標準事務隔離級別值

0 ~ 1

開啓事務(Begin)

setAutoCommit(false) set autommit=0; 1
執行SQL 多條SQL逐步執行 -- 0

提交或回滾事務

 

commit()/rollback() commit; rollback; 1
恢復連接自動提交狀態 setAutoCommit(true) set autocommit=1 1
恢復事務隔離級別(Begin) setTransactionIsolation()

SET SESSION TRANSACTION ISOLATION LEVEL  <隔離級別>

 

隔離級別參考上一個表中:數據庫標準事務隔離級別值

0 ~ 1
恢復連接爲讀寫事務 setReadOnly(false) set session transaction read write; 1

在這種情況下增加的DB通信次數最壞情況是原來的:7倍或9倍,也就是2ms的SQL執行可能變成14ms或18ms,100ms的網絡延遲可能會辦成700ms或900ms。這是最壞情況了嗎?除了事務外,應用對DB的操作還有很多隱藏操作會直接或間接增加通信次數,例如:setMaxRows(xx)、enableStreamingResults()/setFetchSize(Integer.MIN_VALUE)、SQL增加前綴/* ping */、開啓服務端遊標、開啓服務端編譯等行爲都會在執行SQL時增加通信次數。也就是通信次數最壞可能會達到10倍以上

 

增加交互次數的潛在問題

  • 業務整體訪問效率降低:當普通的SQL操作被數倍放大後,業務整體響應效率自然會降低,至於降低的比例是根據業務在操作DB層面的整體時間佔比情況決定的(常規的業務佔比是很大的)。
  • 數據庫連接數被打滿風險:由於處理事務時間變長數倍,所以同等的業務併發量需要更大的連接數來支撐,對等到數據庫作爲公共資源連接數就會倍增,可能會將連接數打滿的概率變高,乃至用完機器上所有的端口也有可能。
  • 讀寫分離幾乎無法實現:至少很難實現同一個應用進程的讀寫分離(同一個應用拆分成只讀、讀寫兩類獨立部署方式),同一個進程在事務內如果要查詢寫入的數據,是需要路由到寫庫上完成的,否則事務未提交前備庫上的另一個會話是讀不到數據的(拋開延遲本身的問題),假設真的可以實現,此時主備上都會開啓事務,延遲次數會再次double。
  • 分庫分表的延遲會再次放大:分庫分表在大多數公司一般是在庫級別或實例級別的事務,所以單個Connection會單獨開啓事務,那麼涉及到多個Connection對數據庫的操作時,事務需要對不同的Connection分別操作,所以延遲次數會再次倍增(根據操作的Connection數量決定)。

 

沒必要開事務的場景

  • 單SQL場景:無論單SQL是DML還是DDL,本身就是彼此之間隔離的,要麼成功要麼失敗,查詢語句基於數據庫事務隔離級別即可。
  • 1~N條查詢:多條語句無論是任何事務隔離級別,是否開啓事務查出來的結果一般都是一樣的(特殊情況是可重複讀場景下且同一個事務內執行了2次同樣的查詢SQL),多條SQL發起的時間點本身不同,所以返回結果是符合條件的。
  • 1~N條查詢+1條修改:同上查詢語句的結果常規情況下並不會改變,所以基於查詢結果做判定處理都是沒有問題的。
  • 0~N條查詢+2~N條無業務強事務要求:同上,由於多條修改無事務強訴求,例如有些是記錄某些行爲日誌或不重要的信息,即使沒記錄成功也沒關係,也可以不用事務。

 

總結及建議

  • 全局開啓事務,雖然配置簡單了,但是會因爲大量不需要開啓事務的地方開啓了事務導致很多潛在的問題或坑,延遲放大的倍數可能比我們想象當中要多得多,所以建議沒必要開啓事務的地方就不要開啓事務,要麼通過Annotation來控制,要麼把需要開啓事務的方法進行特殊命名再進行AOP切入。

  • 在大部分的業務當中相信是沒必要開啓事務的,或者說業務上真正需要開啓事務操作的業務場景其實佔比並不大。也就是說在當全局開啓事務,一般對業務的整體服務是有一定影響的。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章