前言:
業務要求一個簡單的併發控制,使得一條數據只被確認一次,我的方案是 悲觀鎖,就是在事務中先對數據行加鎖(MySQL InnoDB 行鎖基於索引),判斷是否已經確認過,未確認的情況下確認,已確認則事務提交釋放鎖。代碼寫完,結果發現未生效,就開始了滿腦子問號的排查過程。
業務代碼結構如下:
//不要這麼做
//一沒對異常進行處理
//二事務的範圍太大包含很多不需要在事務中的代碼
@Transactional
public void dangerConfirm(ConfirmDangerRequest request) throws CommonException {
String zhiXinBianHao = request.getZhiXinBianHao();
//此處方法中包含一次查詢操作 查詢表 A數據
CodeWxid codeWxid = hisDaoService.getCode(request.getConfirmUserId());
if (codeWxid == null || codeWxid.getWxId() == null){
throw new CommonException("工號或企業微信id有問題", ResultStatusCode.INVALID_CAPTCHA);
}
//在此行打斷點先阻塞
//悲觀鎖 鎖表B中的一行數據 mybatis
String lock = someMapper.lock("AED5ADC3C67E4A89AB7161DA84DC1FC1");
System.out.println(lock);
//用JPA查同一條數據 偷懶 表B
BaseInfo baseInfo = baseInfoRepository.findByZhiXinBianHao("AED5ADC3C67E4A89AB7161DA84DC1FC1");
//用mybatis查詢同一行的某個字段 表B
String te = someMapper.te("AED5ADC3C67E4A89AB7161DA84DC1FC1");
情況描述:
在MySQL命令行直接開事務,鎖同一行,此時上文代碼斷點往下執行會等待鎖的釋放,正常。在命令行事務中更新數據中的某個字段,後提交。此時,調試代碼獲取到鎖,向下執行時 發現問題:最後兩行,均未查出命令行已經提交的字段的值,即 無法讀到其它事務已經提交的數據。這和我所掌握的知識不符。
分析:
數據庫MySQL的隔離級別時 RR,不會出現髒讀和不可重複讀。問題是現在其它事務提交的都讀不到,但是數據庫軟件是可以查到的。沒辦法了,排除法,把所有與事務無關的註釋掉,一執行,好了。。。。。。可以正常查到其它事務已提交的數據。
那麼,自然地就定位到 下面這行的問題
//此處方法中包含一次查詢操作 查詢表 A數據
CodeWxid codeWxid = hisDaoService.getCode(request.getConfirmUserId());
加上上面這行,又不行了。。。。。除了悲觀鎖那行外,後面又讀不到其它事務提交的數據了。。
發現了這個現象,下面就開始做實驗(隔離級別爲 REPEATABLE-READ):
起兩個MySQL命令行客戶端A、B,兩邊都 set autocommit = 0; start transaction;
實驗一:A更新一條數據行 id = 1,值更新爲 99,此時A不提交,B是查不到新值99的。A提交,B直接查詢此條數據,可以查到值99。注意,在此之前B從未執行過查詢操作。
實驗一:A更新一條數據行 id = 1,值更新爲 99,此時A不提交,B是查不到新值99的。A提交,B先任意執行一條查詢,再查詢此條id=1的數據,就不可以查到值99,查到的是之前的舊值。
這好像很符合 REPEATABLE-READ隔離級別的定義
使用 set @@session.tx_isolation='read-committed'; 將AB會話的隔離級別調整爲 read-committed。發現:B事務任何情況下都可以讀到A事務剛提交的最新數據。
總結:
MySQL默認的隔離級別爲 REPEATABLE-READ,這個隔離級別使得 前後讀取同一條的值是相同的,不會受其它事務的影響,除非它自己改變的。