mysql乐观锁重试_乐观锁加重试,并发更新数据库一条记录导致:Lock wait timeout exceeded...
背景:
mysql數(shù)據(jù)庫(kù),用戶(hù)余額表有一個(gè)version(版本號(hào))字段,作為樂(lè)觀鎖。
更新方法有事務(wù)控制:
@Transactional(rollbackFor = Exception.class)
更新時(shí),比對(duì)版本號(hào),如果版本號(hào)不一致,則更新失敗。
有重試機(jī)制,如果更新失敗,則查詢(xún)最新版本號(hào),再次更新,重試超過(guò)5次,報(bào)錯(cuò)退出。
更新的核心方法:
public boolean updateUserAccount(Long userId, int amount) {
boolean retryable;
int attemptNumber = 0;
do {
// 查詢(xún)最新版本號(hào)
UserAccount userAccount = accountMapper.selectByPrimaryKey(userId);
long oldVersion = userAccount.getVersion();
// 更新
boolean success = accountMapper.updateBalance(amount, new Date(), userId, oldVersion) > 0;
if (success) {
return true;
} else {
attemptNumber++;
retryable = attemptNumber < 5;
if (attemptNumber == 5) {
log.error("超過(guò)最大重試次數(shù)");
break;
}
try {
Thread.sleep(300);
} catch (InterruptedException e) {
log.error(e);
}
}
} while (retryable);
return false;
}
更新語(yǔ)句:
UPDATE user_account
SET
balance = balance - #{amount,jdbcType=INTEGER},
update_time = #{updateTime,jdbcType=TIMESTAMP},
version = #{version,jdbcType=BIGINT} + 1
WHERE balance > #{amount,jdbcType=INTEGER}
AND user_id = #{userId,jdbcType=BIGINT}
AND version = #{version,jdbcType=BIGINT};
在并發(fā)更新時(shí),報(bào)異常:Lock wait timeout exceeded
分析:
根據(jù)日志分析出:
線程a、b幾乎同時(shí)到達(dá)
線程a查詢(xún)版本號(hào):856
線程a更新數(shù)據(jù)庫(kù):成功
數(shù)據(jù)庫(kù)當(dāng)前版本號(hào):857
線程b查詢(xún)到的版本號(hào):856(實(shí)際已不是最新)
線程b更新數(shù)據(jù)庫(kù):失敗
線程b重試,查詢(xún)版本號(hào):856
線程b更新數(shù)據(jù)庫(kù):失敗
。。。
線程b超過(guò)重試次數(shù),退出
線程b重試的過(guò)程中,又有其他線程到來(lái),比如c,d,e
線程c查詢(xún)版本號(hào):857
線程c更新數(shù)據(jù)庫(kù):阻塞,因?yàn)閎拿到鎖一直在重試
線程d查詢(xún)版本號(hào):857
線程d更新數(shù)據(jù)庫(kù):阻塞,因?yàn)閎拿到鎖一直在重試
線程b超次數(shù)退出后,c,d,e爭(zhēng)搶鎖
d拿到鎖,更新數(shù)據(jù)庫(kù):成功
數(shù)據(jù)庫(kù)當(dāng)前版本號(hào):858
線程c查詢(xún)到的版本號(hào):857(實(shí)際已不是最新)
線程c更新數(shù)據(jù)庫(kù):失敗
線程c重試,查詢(xún)版本號(hào):857
線程c更新數(shù)據(jù)庫(kù):失敗
。。。
線程c超過(guò)重試次數(shù),退出
重試次數(shù)過(guò)多,事務(wù)執(zhí)行時(shí)間超過(guò)mysql默認(rèn)的鎖等待時(shí)間(50s),就會(huì)報(bào)出:Lock wait timeout exceeded
為什么線程讀不到最新的版本號(hào)呢?原來(lái)是用到了事務(wù),且mysql默認(rèn)事務(wù)隔離級(jí)別Repeatable?Read,把隔離級(jí)別改為READ_COMMITTED,問(wèn)題解決:
@Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
分析了這么多,解決問(wèn)題其實(shí)只需要一行代碼。
總結(jié)
以上是生活随笔為你收集整理的mysql乐观锁重试_乐观锁加重试,并发更新数据库一条记录导致:Lock wait timeout exceeded...的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: java.net.url 中文乱码,.N
- 下一篇: python文本特征选择,机器学习--特