首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 數據庫知識 >  >> MySQL 資料庫 >> MySQL 綜合教學

高併發下 MySQL「查詢-插入」競態條件解決方案:從唯一索引到分佈式鎖的演進之路

     編輯:MySQL 綜合教學

高併發下 MySQL「查詢-插入」競態條件解決方案:從唯一索引到分佈式鎖的演進之路

業務場景:車輛 VIN 碼接入檢查,若數據庫無記錄或狀態=1(已完成),則插入狀態=0(處理中)並觸發人工處理;若狀態=0 則駁回。單線程無問題,高併發下「查詢不存在 → 並發插入」導致臟數據。本文對比四種方案優劣,給出生產級選型建議。

方案一:數據庫唯一索引 + 異常捕獲(最簡潔,但受限業務)

ALTER TABLE vin_table ADD UNIQUE INDEX uk_vin_status (vin, status);
  • ✅ 原子性強、無額外組件、性能高。
  • ❌ 業務要求「VIN 可重複、查最新」,無法在 VIN 上建唯一索引 → 方案不可行。

方案二:數據庫事務包裹(無法解決併發競態)

START TRANSACTION;
SELECT status FROM vin_table WHERE vin=? FOR UPDATE;  -- 行鎖
IF NOT EXISTS OR status=1 THEN INSERT ...; END IF;
COMMIT;
  • ❌ SELECT ... FOR UPDATE 鎖住的是「已存在的行」,對「不存在的行」無鎖定能力(間隙鎖需 REPEATABLE READ 且索引覆蓋),兩並發事務均查不到 → 均插入 → 仍會臟數據。

方案三:Java 進程內鎖(單機有效,集群失效)

// synchronized 或 ReentrantLock
synchronized(this) {
    if (!exists(vin)) insert(vin);
}
  • ✅ 單機單 JVM 完美解決。
  • ❌ 多實例部署時,鎖不跨進程 → 集群下失效。
  • ❌ 鎖粒度粗(鎖住整個方法),吞吐受限。

方案四:ConcurrentHashMap 記錄「進行中」VIN(單機生產可用,需配合業務時間窗)

private static final ConcurrentHashMap<String, String> PROCESSING_VINS = new ConcurrentHashMap<>();

public boolean tryInsertVin(String vin) {
    // 嘗試放入,若已存在說明有並發請求正在處理
    String prev = PROCESSING_VINS.putIfAbsent(vin, Thread.currentThread().getName());
    if (prev != null) {
        return false; // 駁回:已有線程在處理該 VIN
    }
    try {
        if (!dbExists(vin) || dbStatus(vin) == 1) {
            dbInsert(vin, 0);
            return true;
        }
        return false;
    } finally {
        PROCESSING_VINS.remove(vin); // 釋放
    }
}
  • ✅ 無鎖、高性能、單機線程安全。
  • ⚠️ 致命缺陷:若應用重啟/重新部署,Map 清空,正在處理的 VIN 丟失保護 → 可能重複插入。需業務允許「重啟窗口期無併發」或配合持久化(Redis)。

方案五:Redis 分佈式鎖(生產集群標準解)

// Redis SET NX EX 實現分佈式鎖
boolean lock = redisTemplate.opsForValue().setIfAbsent("lock:vin:" + vin, requestId, 30, TimeUnit.SECONDS);
if (!lock) return REJECT_CONCURRENT;

try {
    // 雙重檢查(防止鎖超時釋放後的極端並發)
    if (!dbExists(vin) || dbStatus(vin) == 1) {
        dbInsert(vin, 0);
        return SUCCESS;
    }
    return REJECT_DUPLICATE;
} finally {
    // Lua 腳本安全釋放鎖(僅刪除自己持有的鎖)
    String lua = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
    redisTemplate.execute(new DefaultRedisScript<>(lua, Boolean.class), "lock:vin:" + vin, requestId);
}
  • ✅ 跨進程、跨機房、高可用(Redis Cluster/Sentinel)。
  • ✅ 鎖自動過期防死鎖,Lua 腳本保證釋放原子性。
  • ✅ 性能極高(微秒級),吞吐無損耗。

方案六:數據庫層面「插入或更新」原子化(MySQL 8.0.19+ / MariaDB)

  維度唯一索引進程鎖ConcurrentHashMapRedis 分佈式鎖ON DUPLICATE KEY
  集群支援✅❌❌✅✅
  業務約束需唯一鍵無無需 Redis需唯一鍵
  性能極高高(單機)極高高極高
  實現複雜度低低中中低
  推薦指數⭐⭐⭐ (條件允許)⭐ (單機)⭐⭐ (單機+可重啟)⭐⭐⭐⭐⭐ (標準)⭐⭐⭐⭐⭐ (條件允許)


最佳實踐建議

  1. 優先評估能否在業務鍵上建立唯一索引 → 用方案一或六,最簡單可靠。
  2. 若業務不允許唯一索引 → 引入 Redis 分佈式鎖(方案五),通用性最強。
  3. 單機部署且重啟可控 → ConcurrentHashMap(方案四)輕量高效。
  4. 嚴禁使用進程鎖(方案三)應對集群併發,極大隱患。
  5. 所有方案均應在「插入前」做雙重檢查,防止鎖超時/失效極端情況。

併發控制的核心是將「檢查-插入」原子化,選對工具,事半功倍。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved