偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

解決重復(fù)下單問題,常提到"一鎖二判三更新"到底是什么?

開發(fā) 后端
我們在聊到后端并發(fā)問題,或者說重復(fù)下單問題的時候,經(jīng)常提到"一鎖二判三更新".這個"一鎖而判三更新",到底是什么呢?本文田螺哥跟大家聊聊哈!

前言

大家好,我是田螺

我們在聊到后端并發(fā)問題,或者說重復(fù)下單問題的時候,經(jīng)常提到"一鎖二判三更新".這個"一鎖而判三更新",到底是什么呢?本文田螺哥跟大家聊聊哈~~

  • 什么是一鎖二判三更新
  • 為什么需要一鎖二判三更新?
  • 不同鎖策略下的實現(xiàn)差異

1.什么是一鎖二判三更新

其實,它是一套處理并發(fā)更新數(shù)據(jù)的標準流程:

  • 一鎖:表示先獲取鎖,保證同一時間只有一個操作能執(zhí)行
  • 二判:檢查數(shù)據(jù)狀態(tài)是否符合預(yù)期,防止臟更新
  • 三更新:確認無誤后執(zhí)行數(shù)據(jù)更新操作

比如扣庫存的場景,我們來看一個一鎖二判三更新的代碼例子:

//一鎖二判三更新的代碼使用例子
@Transactional
public boolean deductStock(Long productId, int quantity) {
    // 一鎖:獲取商品的行鎖
    // 使用for update進行悲觀鎖鎖定,確保同一時間只有一個事務(wù)能操作該商品
    Product product = productMapper.selectForUpdateById(productId);
    if (product == null) {
        throw new RuntimeException("商品不存在");
    }
    
    // 二判:判斷庫存是否充足
    if (product.getStock() < quantity) {
        throw new RuntimeException("庫存不足,當(dāng)前庫存:" + product.getStock());
    }
    
    // 三更新:執(zhí)行庫存扣減
    int newStock = product.getStock() - quantity;
    product.setStock(newStock);
    int rows = productMapper.updateById(product);
    
    return rows > 0;
}

對應(yīng)的 MyBatis update更新方法:

<select id="selectForUpdateById" resultType="com.example.Product">
    select * from product where id = #{id}
    for update
</select>

2. 為什么需要一鎖二判三更新

在并發(fā)場景,為什么需要一鎖二判三更新這套使用流程呢?

假設(shè)類似這種場景


兩個用戶同時給同一個商品下單,而商品僅剩最后一件庫存。如果沒有加鎖,可能會出現(xiàn)兩個訂單都創(chuàng)建成功,但實際庫存不足的情況。

同理,也是這個場景,假設(shè)你加鎖了,如果沒有這個二判(判斷庫存是否充足),依然可能會出現(xiàn)兩個訂單創(chuàng)建都成功的情況。

錯誤使用例子:

// 錯誤示例:無鎖無判斷
public boolean deductStock(Long productId, int quantity) {
    // 1. 查詢當(dāng)前庫存
    Product product = productMapper.selectById(productId);
    
    // 2. 直接扣減庫存(未判斷是否充足)
    int newStock = product.getStock() - quantity;
    product.setStock(newStock);
    
    // 3. 更新庫存
    return productMapper.updateById(product) > 0;
}

因此,"一鎖二判三更新" 正是為這類并發(fā)場景設(shè)計的解決方案。單獨使用鎖或單獨做判斷都無法徹底解決問題,必須三者結(jié)合。

3. 不同鎖策略下的實現(xiàn)差異

一鎖二判三更新中的一鎖,其實有不同的實現(xiàn)方式的,既有悲觀鎖,也有樂觀鎖。

對于悲觀鎖,適合寫操作比較頻繁、沖突概率高的場景:

  • 優(yōu)點:實現(xiàn)簡單,沖突處理直接
  • 缺點:可能導(dǎo)致鎖等待,并發(fā)性能較低

比如,我們前面第一小節(jié)的就是悲觀鎖哈實現(xiàn)方式哈

<select id="selectForUpdateById" resultType="com.example.Product">
    select * from product where id = #{id}
    for update
</select>

如果讀操作頻繁、寫操作沖突概率低的場景,則更適合用樂觀鎖。簡單demo代碼如下:

// 樂觀鎖實現(xiàn):一鎖(版本控制)二判三更新
public boolean deductStock(Long productId, int quantity) {
    // 獲取當(dāng)前商品信息(包含版本號)
    Product product = productMapper.selectById(productId);
    if (product == null) {
        throw new RuntimeException("商品不存在");
    }
    
    // 二判:判斷庫存是否充足
    if (product.getStock() < quantity) {
        throw new RuntimeException("庫存不足");
    }
    
    // 準備更新數(shù)據(jù)
    int newStock = product.getStock() - quantity;
    product.setStock(newStock);
    // 版本號+1(樂觀鎖的關(guān)鍵)
    product.setVersion(product.getVersion() + 1);
    
    // 執(zhí)行更新(WHERE條件包含版本號,相當(dāng)于一鎖的實現(xiàn))
    int rows = productMapper.updateWithVersion(product);
    
    // 如果更新行數(shù)為0,說明版本號已變,并發(fā)沖突
    if (rows == 0) {
        throw new RuntimeException("并發(fā)更新沖突,請重試");
    }
    
    returntrue;
}

很多時候,解決并發(fā)問題,我們使用的是Redis分布式鎖。

在分布式系統(tǒng)中,"一鎖" 通常會升級為分布式鎖,而 "二判" 在庫存場景下核心就是判斷庫存是否滿足需求。

以 Redis 分布式鎖為例,實現(xiàn)分布式環(huán)境下的庫存扣減簡單代碼如下:

// 分布式環(huán)境下的"一鎖二判三更新"
public boolean deductStock(Long productId, int quantity) {
    // 分布式鎖的key,通常用業(yè)務(wù)標識+ID
    String lockKey = "stock:lock:" + productId;
    // 生成唯一標識,用于釋放鎖時的身份驗證
    String requestId = UUID.randomUUID().toString();
    
    try {
        // 一鎖:獲取分布式鎖(演示用的)
        // 第三個參數(shù)是超時時間,防止死鎖
        boolean locked = redisTemplate.opsForValue()
            .setIfAbsent(lockKey, requestId, 10, TimeUnit.SECONDS);
        
        if (!locked) {
            // 獲取鎖失敗,說明有其他進程正在操作,返回失敗或重試
            returnfalse;
        }
        
        // 二判:查詢并判斷庫存
        Product product = productMapper.selectById(productId);
        if (product == null) {
            throw new RuntimeException("商品不存在");
        }
        if (product.getStock() < quantity) {
            throw new RuntimeException("庫存不足,當(dāng)前庫存:" + product.getStock());
        }
        
        // 三更新:扣減庫存
        int newStock = product.getStock() - quantity;
        product.setStock(newStock);
        int rows = productMapper.updateById(product);
        
        return rows > 0;
    } finally {
        // 釋放分布式鎖(需要驗證身份,防止誤刪其他進程的鎖)
        if (requestId.equals(redisTemplate.opsForValue().get(lockKey))) {
            redisTemplate.delete(lockKey);
        }
    }
}


責(zé)任編輯:武曉燕 來源: 撿田螺的小男孩
相關(guān)推薦

2022-10-09 07:33:38

JavaSPI程序

2024-11-11 10:15:04

CPULinux系統(tǒng)

2024-05-13 12:44:00

InnodbMySQL行級鎖

2011-04-27 09:30:48

企業(yè)架構(gòu)

2020-09-27 06:53:57

MavenCDNwrapper

2020-10-14 06:22:14

UWB技術(shù)感知

2010-11-01 01:25:36

Windows NT

2020-09-22 08:22:28

快充

2020-08-04 14:20:20

數(shù)據(jù)湖Hadoop數(shù)據(jù)倉庫

2019-10-30 10:13:15

區(qū)塊鏈技術(shù)支付寶

2013-06-09 09:47:31

.NetPDBPDB文件

2010-04-22 14:14:29

Live-USB

2021-09-03 09:12:09

Linux中斷軟件

2021-01-21 21:24:34

DevOps開發(fā)工具

2021-09-01 23:29:37

Golang語言gRPC

2021-02-05 10:03:31

區(qū)塊鏈技術(shù)智能

2024-02-04 00:01:00

云原生技術(shù)容器

2023-07-12 15:32:49

人工智能AI

2021-07-07 05:07:15

JDKIterator迭代器

2022-10-08 00:00:00

Spring數(shù)據(jù)庫項目
點贊
收藏

51CTO技術(shù)棧公眾號