Spring事務(wù)原理解析
前言
最近在編寫公司APP產(chǎn)品的商品砍價功能,其中有一個接口涉及并發(fā)訪問。自測時通過ApiFox接口管理工具進(jìn)行壓測,落地數(shù)據(jù)時出現(xiàn)了"鎖失效"的情景。十分感謝后端小伙伴的幫助排查,解決了這個問題。
問題描述
并發(fā)接口中,先對主表數(shù)據(jù)進(jìn)行讀取,進(jìn)行業(yè)務(wù)判斷后,新增、修改它表的數(shù)據(jù)。在理應(yīng)串行執(zhí)行的情況下發(fā)生了多個請求線程讀取到了相同的主表數(shù)據(jù),導(dǎo)致數(shù)據(jù)處理異常。也正是前言中所說的"鎖失效"了。(實際情況加鎖操作是有效的)
代碼復(fù)現(xiàn)
@RequestMapping("/test") @Transactional(rollbackFor = Exception.class) public String test() { DistributedLock.lock("ct_lock"); try { Map<String, Object> resultMap = jdbcTemplate.queryForMap("select * from concurrent_read_uncommit"); int num = Integer.parseInt(resultMap.get("num").toString()); num++; jdbcTemplate.update("update concurrent_read_uncommit set num = " + num); } finally { DistributedLock.unlock("ct_lock"); } return "success"; }
- 最少的代碼進(jìn)行演示,Controller方法體中的內(nèi)容應(yīng)是Service中的代碼
- DistributedLock中封裝的Redission
- 通過將先讀后改的方式演示,實際中本質(zhì)就是進(jìn)行了這樣的操作,但會存在更多的業(yè)務(wù)代碼(不演示新增的情況)
ApiFox中通過創(chuàng)建100個請求線程進(jìn)行壓測,最終concurrent_read_uncommit表中的num字段值為94,而非100。
排查
1. 鎖失效
新編寫了兩個簡單接口,第一個接口加鎖,并線程休眠30秒后釋放鎖。另一個接口加同樣的鎖,打印一條語句后直接返回。先調(diào)用第一個接口,在調(diào)用第二個接口。Debug中發(fā)現(xiàn)鎖是有效的,在redis中存有鎖Key。并且訪問第二個接口時,線程被阻塞在了加鎖行代碼。
2. 事務(wù)隔離級別
查詢數(shù)據(jù)庫事務(wù)默認(rèn)隔離級別:
select @@tx_isolation;
結(jié)果
REPEATABLE-READ
就是默認(rèn)的RR級別,那么說明同個事務(wù)內(nèi)多次讀取數(shù)據(jù)都會是一樣的,不會讀取到臟數(shù)據(jù)。
3. 修改Spring事務(wù)傳播配置
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
與這個并沒有關(guān)系,八竿子打不著。當(dāng)時的想法時是多個并發(fā)請求在進(jìn)入到了同個事務(wù)內(nèi),并一起讀取到了沒有被修改前的數(shù)據(jù)。細(xì)想想:
- 事務(wù)傳播配置一般用在不同事務(wù)方法間產(chǎn)生調(diào)用時的事務(wù)決策,是共用事務(wù)還是新創(chuàng)建事務(wù),亦或是其他的方式進(jìn)行處理
- test方法本身為根方法,也沒有調(diào)用其他的事務(wù)方法,所以無需配置事務(wù)傳播配置
- 即便不在同一事務(wù)內(nèi),依舊能查詢到其他事務(wù)修改但未提交的相同數(shù)據(jù)
解決方案
在鎖代碼塊中調(diào)用事務(wù)方法,而不是在事務(wù)方法中進(jìn)行加鎖。
原因為:并發(fā)情境下,執(zhí)行速度過快,很有可能發(fā)生:請求線程在釋放鎖后沒有來得及提交事務(wù),另一個請求線程在加鎖處被喚醒,繼而讀取到了事務(wù)未提交的數(shù)據(jù)。即讀取到了臟數(shù)據(jù),產(chǎn)生了"鎖失效"的效果。
修正代碼:
@RequestMapping("/test2") public String test2() { ConcurrentTransactionalController proxyBean = SpringContextUtils.getBean(this.getClass()); proxyBean.doTest2(); return "success"; } @Transactional(rollbackFor = Exception.class) public void doTest2() { DistributedLock.lock("ct_lock"); try { Map<String, Object> resultMap = jdbcTemplate.queryForMap("select * from concurrent_read_uncommit"); int num = Integer.parseInt(resultMap.get("num").toString()); num++; jdbcTemplate.update("update concurrent_read_uncommit set num = " + num); Thread.sleep(500); } catch (InterruptedException e) { throw new RuntimeException(e); } finally { DistributedLock.unlock("ct_lock"); } }
- 將需要加鎖的事務(wù)代碼進(jìn)行提取另一個方法
- 調(diào)用方法中進(jìn)行加鎖,并且必須要去掉事務(wù)注解
- 因為是在非事務(wù)方法調(diào)用事務(wù)方法,為了保證事務(wù)生效,需要通過事務(wù)代理Bean進(jìn)行調(diào)用
這樣就保證了不會讀取到事務(wù)未提交的數(shù)據(jù),同時又具有鎖的排他性。
其實鎖一直都是有效的,本質(zhì)原因就在于Spring的事務(wù)代理Bean屏蔽了事務(wù)代碼。我們不能手動的進(jìn)行控制,也就是說你變更了不了事務(wù)代碼的順序。如果能將提交事務(wù)的行代碼寫到釋放鎖之前,就不會存在這個問題了。所以,也可以通過編程式事務(wù)解決這個問題,關(guān)于編程式事務(wù),Spring也有做代碼封裝。如果不通過編程式事務(wù),那么就只能通過上述代碼變相的來實現(xiàn)。
到此這篇關(guān)于Spring事務(wù)原理解析的文章就介紹到這了,更多相關(guān)Spring事務(wù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java增強(qiáng)for循環(huán)的增刪操作代碼
Foreach循環(huán)(Foreach loop)是計算機(jī)編程語言中的一種控制流程語句,通常用來循環(huán)遍歷數(shù)組或集合中的元素,本文通過實例演示普通for循環(huán)和foreach循環(huán)使用,java增強(qiáng)for循環(huán)的操作代碼感興趣的朋友一起看看吧2024-02-02java實現(xiàn)Excel的導(dǎo)入、導(dǎo)出
這篇文章主要為大家詳細(xì)介紹了java實現(xiàn)Excel的導(dǎo)入、導(dǎo)出的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-06-06springmvc fastjson 反序列化時間格式化方法(推薦)
下面小編就為大家?guī)硪黄猻pringmvc fastjson 反序列化時間格式化方法(推薦)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-04-04淺析IDEA如何正確配置Gradle? GRADLE_USER_HOME 和 Gradle user home的區(qū)別
這篇文章主要介紹了IDEA如何正確配置Gradle? GRADLE_USER_HOME 和 Gradle user home的區(qū)別,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-08-08mybatis攔截器無法注入spring bean的問題解決
本文主要介紹了mybatis攔截器無法注入spring bean的問題解決,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-02-02Java NIO 文件通道 FileChannel 用法及原理
這篇文章主要介紹了Java NIO 文件通道 FileChannel 用法和原理,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-01-01Gradle構(gòu)建基本的Web項目結(jié)構(gòu)
這篇文章主要為大家介紹了Gradle創(chuàng)建Web項目基本的框架結(jié)構(gòu)搭建,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03