網(wǎng)站首頁 編程語言 正文
問題: 這段代碼會不會有問題
@Override
public void increaseSpecialPrizeNum(String city) {
// 通過城市名稱查詢城市
LambdaQueryWrapper<City> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(City::getCityName, city);
City cityDb = this.getOne(queryWrapper);
if (cityDb == null) {
throw new ServiceException("城市不存在");
}
// 特等獎數(shù)量加1
cityDb.setWinningQuotaFinish(cityDb.getWinningQuotaFinish() + 1);
this.updateById(cityDb);
}
回答: 會存在問題
提供的代碼片段確實可能在高并發(fā)情況下遇到問題,尤其是在更新城市的已中獎數(shù)量(winningQuotaFinish
)時。問題的核心在于多個并發(fā)請求可能同時讀取同一城市的當前已中獎數(shù)量,并基于這個值進行增加操作,而這可能導(dǎo)致一種稱為“丟失更新”的并發(fā)問題。
解決這類問題的常見方法包括:
1. 使用樂觀鎖
樂觀鎖是一種常用的并發(fā)控制方法,特別適合讀多寫少的場景。您可以在 City
實體中添加一個版本號字段(例如 version
),并在每次更新時自動增加這個版本號。更新操作將基于版本號,確保在更新期間數(shù)據(jù)未被其他事務(wù)更改。示例代碼如下:
public void updateCity(String city) {
LambdaQueryWrapper<City> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(City::getCityName, city);
City cityDb = this.getOne(queryWrapper);
if (cityDb == null) {
throw new ServiceException("城市不存在");
}
int currentVersion = cityDb.getVersion();
cityDb.setWinningQuotaFinish(cityDb.getWinningQuotaFinish() + 1);
LambdaUpdateWrapper<City> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(City::getId, cityDb.getId())
.eq(City::getVersion, currentVersion)
.set(City::getWinningQuotaFinish, cityDb.getWinningQuotaFinish())
.set(City::getVersion, currentVersion + 1);
boolean updated = this.update(updateWrapper);
if (!updated) {
// 更新失敗,可能是因為并發(fā)修改
throw new ServiceException("更新失敗,請重試");
}
}
2. 使用悲觀鎖
悲觀鎖適用于寫操作頻繁的場景。您可以在事務(wù)中鎖定特定的行,直到事務(wù)完成。這通常通過選擇具有鎖定行為的查詢來實現(xiàn)。示例代碼如下:
@Transactional
public void updateCity(String city) {
// 先鎖定行
City cityDb = ... // 使用SELECT ... FOR UPDATE查詢獲取城市數(shù)據(jù)
if (cityDb == null) {
throw new ServiceException("城市不存在");
}
// 更新操作
cityDb.setWinningQuotaFinish(cityDb.getWinningQuotaFinish() + 1);
this.updateById(cityDb);
}
在使用 SELECT ... FOR UPDATE
時,請確保您的事務(wù)管理設(shè)置正確,并注意這種方法可能導(dǎo)致數(shù)據(jù)庫性能下降。
3. 在數(shù)據(jù)庫層面處理
如果使用如 MySQL 這樣的關(guān)系數(shù)據(jù)庫,您可以考慮在數(shù)據(jù)庫層面使用原子更新操作。例如:
UPDATE city SET winning_quota_finish = winning_quota_finish + 1 WHERE city_name = ?
這種方法避免了應(yīng)用層面的并發(fā)問題,因為數(shù)據(jù)庫會處理原子性。
選擇合適的方法
選擇哪種方法取決于您的應(yīng)用特性和環(huán)境。樂觀鎖適合讀多寫少的場景,對性能影響較小,但可能需要處理更新沖突。悲觀鎖適用于更新頻繁的場景,但可能導(dǎo)致數(shù)據(jù)庫性能問題。數(shù)據(jù)庫層面的原子更新最簡單直接,但可能不夠靈活。
額外疑問: 方法3為什么不會出現(xiàn)問題, 我感覺方法3和我的那個方法效果是一樣的呀
回答:
從某種程度上來說,方法3(在數(shù)據(jù)庫層面直接執(zhí)行原子更新操作)和您的原始方法(先讀取數(shù)據(jù),然后在應(yīng)用層面上增加值,最后再更新數(shù)據(jù)庫)在邏輯上是相似的。然而,關(guān)鍵的區(qū)別在于它們處理并發(fā)更新時的行為。
方法3(數(shù)據(jù)庫層面的原子更新操作)
當您直接在數(shù)據(jù)庫層面執(zhí)行一個原子更新操作,如:
UPDATE city SET winning_quota_finish = winning_quota_finish + 1 WHERE city_name = ?
這個操作是原子性的,這意味著數(shù)據(jù)庫會自動處理并發(fā)問題。即使多個請求同時嘗試更新同一行,數(shù)據(jù)庫系統(tǒng)會確保每次更新都是在最新的數(shù)據(jù)上執(zhí)行的,不會有任何中間步驟導(dǎo)致數(shù)據(jù)不一致。數(shù)據(jù)庫內(nèi)部的鎖機制會確保在一個時間點上只有一個操作能夠修改數(shù)據(jù)。
您的原始方法(應(yīng)用層面的讀-修改-寫操作)
在您的原始方法中,更新操作分為三個步驟:
-
讀取數(shù)據(jù):應(yīng)用從數(shù)據(jù)庫中讀取當前的
winning_quota_finish
值。 -
修改數(shù)據(jù):應(yīng)用在內(nèi)存中將
winning_quota_finish
的值增加。 -
寫回數(shù)據(jù):應(yīng)用將新的
winning_quota_finish
值寫回數(shù)據(jù)庫。
在高并發(fā)的場景下,這個過程可能導(dǎo)致所謂的“讀-改-寫”競態(tài)條件。假設(shè)兩個并發(fā)請求幾乎同時讀取了相同的 winning_quota_finish
值,它們都會在內(nèi)存中對這個值進行增加,然后嘗試將其寫回數(shù)據(jù)庫。這可能導(dǎo)致實際的 winning_quota_finish
值只增加了一次,而不是兩次,因為兩個請求都是基于最初讀取的相同值進行操作的。
總結(jié)來說,方法3(數(shù)據(jù)庫層面的原子更新)能更有效地處理并發(fā)情況,因為數(shù)據(jù)庫系統(tǒng)內(nèi)部會管理必要的鎖,以確保每次更新都基于最新的數(shù)據(jù),從而避免了并發(fā)更新導(dǎo)致的問題。而應(yīng)用層面的讀-修改-寫操作在高并發(fā)下則可能導(dǎo)致數(shù)據(jù)不一致,除非采取額外的措施(如樂觀鎖或悲觀鎖)來控制并發(fā)訪問。
原文鏈接:https://blog.csdn.net/weixin_39973810/article/details/134535908
- 上一篇:沒有了
- 下一篇:沒有了
相關(guān)推薦
- 2024-04-06 MyBatis的一級(同SqlSession會話),二級(不同SqlSession會話)緩存使用
- 2022-10-16 Ant?Design?組件庫之步驟條實現(xiàn)_React
- 2023-01-26 Python+Sklearn實現(xiàn)異常檢測_python
- 2023-01-07 Python中導(dǎo)入模塊的幾種方式總結(jié)_python
- 2022-07-21 element 中l(wèi)oading顏色的修改
- 2023-01-23 C#實現(xiàn)懸浮窗口的方法詳解_C#教程
- 2022-07-13 Android Canvas - save() & restore()
- 2022-10-07 numpy中數(shù)組拼接、數(shù)組合并方法總結(jié)(append(),?concatenate,?hstack,
- 欄目分類
-
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支