日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學(xué)無先后,達者為師

網(wǎng)站首頁 編程語言 正文

Mybatis plus并發(fā)更新時的問題

作者:一勺菠蘿丶 更新時間: 2024-01-31 編程語言

問題: 這段代碼會不會有問題

    @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)用層面的讀-修改-寫操作)

在您的原始方法中,更新操作分為三個步驟:

  1. 讀取數(shù)據(jù):應(yīng)用從數(shù)據(jù)庫中讀取當前的 winning_quota_finish 值。
  2. 修改數(shù)據(jù):應(yīng)用在內(nèi)存中將 winning_quota_finish 的值增加。
  3. 寫回數(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

  • 上一篇:沒有了
  • 下一篇:沒有了
欄目分類
最近更新