首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計疑難解答

電商併發下單:資料庫行鎖 + 唯一索引防止同一訂單重複處理

     編輯:程式設計疑難解答

場景:兩用戶同帳號、同訂單、同時提交,服務端如何保證只處理一次?

核心方案:資料庫層面「悲觀鎖」+「唯一約束」雙重保險

1. 訂單表加唯一索引 (業務主鍵)


ALTER TABLE `order` ADD UNIQUE INDEX `uk_user_biz` (`user_id`, `biz_type`, `biz_id`);
-- 同用戶、同業務類型、同業務 ID 只能有一筆訂單

2. 服務層「查詢鎖定 → 檢查 → 寫入」單一事務


@Transactional
public Order submitOrder(Long userId, Long bizId, String bizType) {
    // 1. 悲觀鎖查詢 (SELECT ... FOR UPDATE) 鎖定該用戶相關行
    Order existing = orderMapper.selectForUpdate(userId, bizType, bizId);
    if (existing != null) {
        throw new BizException("訂單已存在: " + existing.getId());
    }
    // 2. 插入 (唯一索引再次攔截併發插入)
    Order order = new Order(userId, bizId, bizType, ...);
    orderMapper.insert(order); // 若併發插入,DB 拋 DuplicateKeyException
    return order;
}

3. MyBatis Mapper




    INSERT INTO `order` (user_id, biz_type, biz_id, ...) 
    VALUES (#{userId}, #{bizType}, #{bizId}, ...)

替代方案:Redis 分散式鎖 (高併發、低延遲)


String lockKey = "lock:order:" + userId + ":" + bizType + ":" + bizId;
boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
if (!locked) throw new BizException("系統繁忙,請稍後重試");
try {
    // 雙重檢查 DB 唯一索引
    return submitOrderDB(userId, bizId, bizType);
} finally {
    redisTemplate.delete(lockKey);
}

方案選型建議

場景推薦方案
中低併發 (QPS < 500)、強一致性要求DB 行鎖 + 唯一索引 (最簡單、ACID 保證)
高併發 (QPS > 5000)、可接受極小概率重複Redis 分散式鎖 + DB 唯一索引雙重檢查
極高併發 (秒殺)、需排隊公平Redis Lua 腳本原子扣庫存 + 異步下單 (MQ)

關鍵原則

  1. **業務唯一性在資料庫層面用唯一索引保證**,應用層邏輯只是優化體驗。
  2. 悲觀鎖 `FOR UPDATE` 只鎖定**索引命中的行**,需確保查詢走索引 (`user_id, biz_type, biz_id`)。
  3. 分散式鎖**僅作為流控優化**,不可替代 DB 唯一約束。

採用「DB 唯一索引 + 悲觀鎖/Redis 鎖」組合拳,即可在高併發下單場景下確保「同一訂單只處理一次」。

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