場景:兩用戶同帳號、同訂單、同時提交,服務端如何保證只處理一次?
ALTER TABLE `order` ADD UNIQUE INDEX `uk_user_biz` (`user_id`, `biz_type`, `biz_id`);
-- 同用戶、同業務類型、同業務 ID 只能有一筆訂單
@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;
}
INSERT INTO `order` (user_id, biz_type, biz_id, ...)
VALUES (#{userId}, #{bizType}, #{bizId}, ...)
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) |
採用「DB 唯一索引 + 悲觀鎖/Redis 鎖」組合拳,即可在高併發下單場景下確保「同一訂單只處理一次」。