Java鎖競爭導(dǎo)致sql慢日志原因分析
線上在同步用戶時,經(jīng)常出現(xiàn)簡單sql的慢日志。根據(jù)方法找到代碼,發(fā)現(xiàn)方法內(nèi)使用redisson進(jìn)行鎖操作,waiTime和leaseTime都為3秒,數(shù)據(jù)庫操作比較簡單,只是一個簡單的用戶更新操作。代碼簡化后如下
@Override
@Transactional(rollbackFor = Exception.class)
public LiveMemberResponseDTO becomeStudentNotQueryOrder(LiveBecomeStudentForOrderRequestDTO requestDTO) {
EduUserEntity user = eduUserService.syncLocalByAccount(requestDTO.getOrderAccountId(), UserRegisterChannel.LIVE_APPLY);
EduUserEntity applyUser = eduUserService.syncLocalByAccount(requestDTO.getApplyAccountId(), UserRegisterChannel.LIVE_APPLY);
...
...
...
}
調(diào)用了更新方法
@Override
@Lock(waitTime = 10000, leaseTime = 3000, value = RedisConstant.USER_SYNC_LOCAL, key = "#accountId")
public EduUserEntity syncLocalByAccount(String accountId, String mobile, String fullName, String source, UserRegisterChannel registerChannel) {
EduUserEntity eduUserEntity = queryUsersByAccountId(accountId);
boolean updateFlag = false;
....
....
....//判斷是否需要更新
if (updateFlag) {
saveOrUpdate(eduUserEntity);
}
return eduUserEntity;
}由于這里事務(wù)里面嵌套了redis鎖,并且涉及到更新表,可能會有死鎖的情況。通過在獲取鎖的地方打上地址獲取到以下日志

可以看到[Thread-13]在等待了三秒后才獲取到redis,根據(jù)獲取鎖的時機,列出表格
| 事務(wù)A(Thread-13) | 事務(wù)B(Thread-14) |
|---|---|
| 獲取redis鎖 | |
| 獲取db鎖 | |
| 釋放redis鎖 | |
| 獲取redis鎖 | |
| 嘗試獲取db鎖 | |
| 嘗試獲取redis鎖 | |
| 等待三秒 | |
| redis鎖超時,釋放redis鎖 | |
| 成功獲取redis鎖 | |
| 完成業(yè)務(wù) | |
| 釋放redis和db鎖 | |
| 獲取db鎖 |
這是由于兩次更新user表過程中,使用了一個事務(wù)A,導(dǎo)致事務(wù)B來獲取db行鎖的時候,被事務(wù)A阻塞。但在被事務(wù)A阻塞前,已經(jīng)獲取到了redis鎖,所以導(dǎo)致事務(wù)A在獲取第二次更新的redis鎖的時候被阻塞,造成了死鎖。

最終導(dǎo)致,redis鎖超時,日志方面體現(xiàn)為慢sql,因為事務(wù)B的sql等待了三秒才拿到鎖。
這種情況是因為事務(wù)和redis鎖的嵌套導(dǎo)致,所以指定更新方法的事務(wù)傳播等級為PROPAGATION_REQUIRED_NEW,不管外層是否存著事務(wù),都開啟新事務(wù)。
修改后代碼
@Override
@Lock(waitTime = 10000, leaseTime = 3000, value = RedisConstant.USER_SYNC_LOCAL, key = "#accountId")
//這里開啟新事務(wù)
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public EduUserEntity syncLocalByAccount(String accountId, String mobile, String fullName, String source, UserRegisterChannel registerChannel) {
EduUserEntity eduUserEntity = queryUsersByAccountId(accountId);
boolean updateFlag = false;
....
....
....//判斷是否需要更新
if (updateFlag) {
saveOrUpdate(eduUserEntity);
}
return eduUserEntity;
}聲明式事務(wù)使用很簡單,可以自動幫我們進(jìn)行事務(wù)的開啟、提交以及回滾等操作,但是事務(wù)的顆粒度是整個方法,無法進(jìn)行精細(xì)化控制。在使用過程中要注意事務(wù)的范圍與其他中間件的交互,通過指定適當(dāng)?shù)膫鞑サ燃墎磉_(dá)到效果。
到此這篇關(guān)于Java鎖競爭導(dǎo)致sql慢日志原因分析的文章就介紹到這了,更多相關(guān)Java sql慢日志內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中Stringbuilder和正則表達(dá)式示例詳解
Java語言為字符串連接運算符(+)提供特殊支持,并為其他對象轉(zhuǎn)換為字符串,字符串連接是通過StringBuilder(或StringBuffer)類及其append方法實現(xiàn)的,這篇文章主要給大家介紹了關(guān)于Java中Stringbuilder和正則表達(dá)式的相關(guān)資料,需要的朋友可以參考下2024-02-02
詳解Java8如何使用Lambda表達(dá)式進(jìn)行比較
Lambda表達(dá)式,也可稱為閉包,是java8的新特性,作用是取代大部分內(nèi)部類,優(yōu)化java代碼結(jié)構(gòu),讓代碼變得更加簡潔緊湊。本文將利用Lambda表達(dá)式進(jìn)行排序比較,需要的可以參考一下2022-01-01
Java上傳文件錯誤java.lang.NoSuchMethodException的解決辦法
今天小編就為大家分享一篇關(guān)于Java上傳文件錯誤java.lang.NoSuchMethodException的解決辦法,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-01-01

