業務場景是這樣的:
調用批量修改用戶金幣的方法userService.batchUpdateAccount(userAccountMap);然後馬上又查詢剛纔修改過金幣的用戶使用userService.getByUserId(1000001).getAccount().getMoney();(用戶和金幣賬號是兩張表 ,getByUserId返回用戶對象,用戶對象裏面引用了賬號對象,用的是mybatis, 不過這並不是重點。),重點是我1000001這個賬號金幣初始是0,增加100金幣後,然後用getByUserId查詢到的還是0, 再重複調用batchUpdateAccount和getByUserId方法得到的金幣是100;就這樣每次得到的都是上一次的金幣量。
查找問題過程:
- 懷疑過是mybatis一級緩存和二級緩存的問題,項目並沒有開二級緩存,一級緩存應該是在一定的時間內查詢的sql一致纔有;然後我又在sql語句上下了點功夫;在sql語句加了個條件
and ${randomStr} = ${randomStr}
,sql再加上個隨機字符串,這樣基本上保證不會觸發一級緩存;不過問題依然沒有解決。 - 還有懷疑過dubbo消費端調用dubbo提供者,提供者這邊把修改的語句延時處理了(但是幾乎不可能)
- 然後就是漫長的看日誌, 對比過程,我發現這兩條sql執行幾乎是同時的(由於隱私問題,我隱藏了重要信息)。
- 懷疑過是mybatis一級緩存和二級緩存的問題,項目並沒有開二級緩存,一級緩存應該是在一定的時間內查詢的sql一致纔有;然後我又在sql語句上下了點功夫;在sql語句加了個條件
2018-06-21 20:06:28.602 |-DEBUG <com.xx.article.mapper.AccountMapper.updateMoneyByAccount> - ==> Preparing: UPDATE xxx_account SET money = money + ?, valid_money = valid_money + ?, play_sum = play_sum + ?, paid_money = paid_money + ?, income_money = income_money + ?, break_even_money = break_even_money + ?, update_time = ? WHERE user_id = ?
2018-06-21 20:06:28.602 |-DEBUG <com.xx.article.mapper.UserInfoVoMapper.selectByUserId> - ==> Preparing: SELECT a.*,b.*,c.* FROM xxx_user_info a LEFT JOIN dwc_account b ON a.user_id=b.user_id LEFT JOIN xxx_level_info c ON a.level_id=c.id WHERE a.user_id = ?
2018-06-21 20:06:28.750 |-DEBUG <com.xx.article.mapper.UserInfoVoMapper.selectByUserId> - ==> Parameters: 1000001(Integer)
2018-06-21 20:06:28.757 |-DEBUG <com.xx.article.mapper.AccountMapper.updateMoneyByAccount> - ==> Parameters: 100(Long), 100(Long), 1(Integer), 0(Long), 100(Long), 100(Long), 2018-06-21 20:06:28.211(Timestamp), 1000001(Integer)
2018-06-21 20:06:28.760 |-DEBUG <com.xx.article.mapper.AccountMapper.updateMoneyByAccount> - <== Updates: 1
2018-06-21 20:06:28.760 |-DEBUG <com.alibaba.druid.pool.PreparedStatementPool> - {conn-10010, pstmt-20000} enter cache
2018-06-21 20:06:28.773 |-DEBUG <com.rw.article.mapper.UserInfoVoMapper.selectByUserId> - <== Total: 1
2018-06-21 20:06:28.774 |-DEBUG <com.alibaba.druid.pool.PreparedStatementPool> - {conn-10009, pstmt-20001} enter cache
- 然後再看了service層的代碼
@Override
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = java.lang.Exception.class)
@Async
public void batchUpdateAccount(Map<Integer, Account> accountList) {
accountList.forEach((k, v) -> accountMapper.updateMoneyByAccount(v));
}
@Override
public UserInfoVo getByUserId(int userId) {
return userInfoVoMapper.selectByUserId(userId);
}
- 發現有個不太熟悉的註解
@Async
,查了一下發現這個是來做異步
的,應該就是它讓兩條語句同時執行了,所以取到的數據都是上次的。把它去掉後日志可以看到是按先後順序執行的sql(由於隱私問題,我隱藏了重要信息):
2018-06-21 20:08:44.892 |-DEBUG <com.xx.article.mapper.AccountMapper.updateMoneyByAccount> - ==> Preparing: UPDATE xxx_account SET money = money + ?, valid_money = valid_money + ?, play_sum = play_sum + ?, paid_money = paid_money + ?, income_money = income_money + ?, break_even_money = break_even_money + ?, update_time = ? WHERE user_id = ?
2018-06-21 20:08:45.002 |-DEBUG <com.xx.article.mapper.AccountMapper.updateMoneyByAccount> - ==> Parameters: 100(Long), 100(Long), 1(Integer), 0(Long), 100(Long), 100(Long), 2018-06-21 20:08:44.724(Timestamp), 1000001(Integer)
2018-06-21 20:08:45.006 |-DEBUG <com.xx.article.mapper.AccountMapper.updateMoneyByAccount> - <== Updates: 1
2018-06-21 20:08:45.006 |-DEBUG <com.alibaba.druid.pool.PreparedStatementPool> - {conn-10010, pstmt-20000} enter cache
2018-06-21 20:08:45.029 |-DEBUG <com.alibaba.dubbo.remoting.transport.DecodeHandler> - [DUBBO] Decode decodeable message com.alibaba.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation, dubbo version: 2.5.6, current host: 127.0.0.1
2018-06-21 20:08:45.030 |-DEBUG <com.xx.article.mapper.UserInfoVoMapper.selectByUserId> - ==> Preparing: SELECT a.*,b.*,c.* FROM xxx_user_info a LEFT JOIN xxx_account b ON a.user_id=b.user_id LEFT JOIN dwc_level_info c ON a.level_id=c.id WHERE a.user_id = ?
2018-06-21 20:08:45.034 |-DEBUG <com.xx.article.mapper.UserInfoVoMapper.selectByUserId> - ==> Parameters: 1000001(Integer)
2018-06-21 20:08:45.056 |-DEBUG <com.xx.article.mapper.UserInfoVoMapper.selectByUserId> - <== Total: 1
2018-06-21 20:08:45.057 |-DEBUG <com.alibaba.druid.pool.PreparedStatementPool> - {conn-10010, pstmt-20001} enter cache
- 最後附上@Async的一點簡介:
Java應用中,絕大多數情況下都是通過同步的方式來實現交互處理的;但是在處理與第三方系統交互的時候,容易造成響應遲緩的情況,之前大部分都是使用多線程來完成此類任務,其實,在spring 3.x之後,就已經內置了@Async來完美解決這個問題