事務隔離級別引發的"血案"
事務引發的"血案"見的多了也麻木了,這回遇到個事務隔離級別的"案子",坑了我小半天的時間...也怪自己細節不牢..
敲著代碼遇到這麽一個怪事情:
class XXXService{ @Transactional public void demo(){ //一堆業務邏輯 rpc.insertOne(); //dubbo調用遠程服務插入一條數據 getOne(); //獲取剛才插入的數據 } }
其中getOne()的事務的傳播屬性是required, 因為dubbo是遠程調用,所以實際上返回後插入的數據就已經commit了, 一個事務中commit的數據另一個事務中應該可以讀取到...本來是這樣的,但是呢getOne()方法中卻讀取不到新插入的數據..
倘使如果不是用dubbo遠程調用插入數據的話, 我可能還不會誤入歧途, 因為數據庫是本地自己搭建的, 平時也沒人改默認配置, 按照以往經驗Mysql默認配置的情況上面的例子應該是可以讀取到的. 所以我把關註點放到這個rpc調用上去了..浪費了很多時間..反正是折騰了很久, 各種推導測試事務傳播,想來想去也應該沒問題.後來幹脆把事務去掉了,發現正常了,傳播屬性各種測試基本確定沒有任何問題了,那麽問題可以確定是在事務隔離性上..饒了一大圈這才剛剛走上正道....
SELECT @@session.tx_isolation;
通過命令查詢了一下mysql數據庫的隔離配置,發現是 Repeatable Read,
這下問題明了,由於代碼中沒有寫明隔離級別,所以使用的是mysql配置的隔離級別,而mysql的隔離級別是可重復讀,故產生了此次問題.
此次問題做一個小結:
產生原因:
查詢數據庫的時候會建立一個到數據庫的連接, 熟稱數據庫session, 有事務的情況下, 這一次連接就處於一個事務中, rpc調用遠程服務,由於不是本地方法,故無法加入本地事務中,所以可以算作是另一個事務,那麽rpc所處的事務插入數據後事務就結束並提交了. 而getOne()方法所處的事務實際上並沒有完成,還受到事務配置的約束,又由於沒有配置事務的隔離屬性,故采用的mysql的隔離配置Repeatable Read, 而這個可重復讀的意思就是一個數據可以反復讀取, 並且讀取到的值不會發生變化, 這實際上就是說當建立此次事務的時候就不會再讀取到新的值了.那麽在事務中途rpc插入的數據也不在getOne()所處事務可以讀取的範圍內, 故讀取不到. 但是如果隔離的屬性是Read Committed的話,則可以讀取到已經提交的數據, rpc服務雖然是中途插入的數據,但是由於新插入的數據已經提交了事務,故依然可以被getOne()方法讀取到.
解決方法:
set global transaction isolation level read committed;
最簡單的方法是直接變更mysql的隔離配置為read committed,這樣就一切歸位了.如果無法變更數據庫隔離級別也依然有辦法,由於隔離配置的生效級別是首先按照程序中配置的級別,其次再看數據庫配置的,那麽在程序中變更隔離級別也可以.
class XXXService{ @Transactional(isolation = Isolation.READ_COMMITTED) public void demo(){ //一堆業務邏輯 rpc.insertOne(); //dubbo調用遠程服務插入一條數據 getOne(); //獲取剛才插入的數據 } }
問題到此就結束了,但是這裏有一個問題比較好奇,Reaptable Read是如何實現的呢,感覺像是開啟事務後就對數據庫進行了一個快照一樣,但是想想又不可能真這樣做,然後百度到了一個介紹mysql mvcc機制的文章.
簡單來說就是實際上在每行數據最後有2列隱藏列,一列代表數據的創建版本,一列代表數據的刪除版本,列中的值存放的是版本號,而這個版本號就是遞增的且和某個事務唯一對應,這樣只要查詢創建版本小於當前版本,刪除版本大於當前版本就可以了,換成白話就是查詢在當前版本之前創建,當前版本之後刪除的數據,這樣就可以保證對於當前版本能查詢到的數據的穩定不可變,而對於修改數據的操作,則是拆分為標記原來的數據為刪除,並插入一條修改後的數據,這樣就完美巧妙的保證修改數據讀取的穩定性.
事務隔離級別引發的"血案"