高并发情况下扣除库存锁表情况

[TOC]

## 1. 锁表情景:查询条件没有索引时

​    库存表中,扣库存时的where条件居然不是id而是奖品类型,而刚好是用事务控制的,微信红包还得调用微信发奖,**导致锁表(间隙锁)**,并且这锁住的时间还是很严重的,每次都得等第三方接口调用成功处理完后续逻辑后,事务才会结束,秒杀场景下就导致了大部分用户超时失败的情况了,以下就是复现了当时的情景

​    总结起来就是两个严重问题:

1. 扣库存时没走索引
2. 在事务中,调第三方接口

```sql
create table gap(
     id int,
     age int,
     primary key(id)
)

select * from gap;
INSERT INTO `test`.`gap`(`id`, `age`) VALUES (1, 13);
INSERT INTO `test`.`gap`(`id`, `age`) VALUES (2, 24);

show engine innodb status;
show status like '%lock%';

show OPEN TABLES where In_use > 0;-- 查询是否锁表,in_use = 1表示加锁,0表示未加锁
show processlist;-- 查询到相对应的进程

-- 查看正在锁的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS; 
-- 查看等待锁的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;

-- 事务1
start transaction;
update gap set age = 25 where age = 24;

-- 事务2
start transaction;
update gap set age = 14 where age = 13;
-- 这一行会等待,age没有索引时,会导致锁表
-- 查看正在锁的事务,为啥不是锁表而是record呢,明明是锁表呀
-- 19988193:890:3:2    19988193    X    RECORD    `test`.`gap`    PRIMARY    890    3    2    1
-- 19988194:890:3:2    19988194    X    RECORD    `test`.`gap`    PRIMARY    890    3    2    1
```

## 2. 解决

​        通过redis实现库存扣减,而不是通过db层来控制库存,减少db的压力。

## 3. 参考:

https://www.cnblogs.com/aspirant/p/9177978.html

> **Gap Lock在InnoDB的唯一作用就是防止其他事务的插入操作,以此防止幻读的发生。**
>
>   **Innodb自动使用间隙锁的条件:**
>
> **(1)必须在Repeatable Read级别下**
>
> **(2)检索条件必须有索引(没有索引的话,mysql会全表扫描,那样会锁定整张表所有的记录,包括不存在的记录,此时其他事务不能修改不能删除不能添加)**

### 使用 Redisson 分布式防止超卖的最佳实践 在高并发场景下,库存管理中的超卖问题是常见的挑战之一。为了有效应对这一问题,可以利用 Redisson 提供的分布式机制来确保操作的一致性和原子性。 #### 1. **Redisson 的 RLock 接口** Redisson 是基于 Redis 实现的强大工具库,提供了多种分布式对象和功能支持。其中 `RLock` 是其核心接口之一,用于实现分布式的互斥[^1]。通过该,可以在多个节点之间协调访问共享资源的行为,从而避免因竞争条件而导致的数据不一致。 #### 2. **代码示例:使用 Redisson 解决库存** 以下是使用 Redisson 进行库存的一个典型例子: ```java import org.redisson.api.RLock; import org.redisson.api.RedissonClient; public class InventoryService { private final RedissonClient redissonClient; public InventoryService(RedissonClient redissonClient) { this.redissonClient = redissonClient; } public void deductInventory(String productId, int quantity) throws InterruptedException { String lockKey = "lock:" + productId; // 定商品ID作为键名 RLock lock = redissonClient.getLock(lockKey); try { boolean isLocked = lock.tryLock(10, 5, TimeUnit.SECONDS); // 尝试获取,等待时间最长为10秒,持有时间为5秒 if (!isLocked) { throw new IllegalStateException("无法获得"); } // 获取当前库存数量 (假设有一个方法可以从数据库读取) int currentStock = getCurrentStock(productId); if (currentStock >= quantity) { updateDatabase(productId, currentStock - quantity); // 更新数据库中的库存量 System.out.println("成功扣除:" + quantity + "件 商品 ID:" + productId); } else { System.out.println("库存不足!"); } } finally { if (lock.isHeldByCurrentThread()) { // 确保只有持有所属线程才能解 lock.unlock(); } } } private int getCurrentStock(String productId){ // 模拟查询数据库逻辑... return 100; // 返回模拟数据 } private void updateDatabase(String productId, int updatedQuantity){ // 执行更新语句到DB... System.out.println("已将产品 "+productId+" 库存量设置为 "+updatedQuantity); } } ``` 上述代码展示了如何借助 Redisson 来保护关键路径上的业务逻辑执行过程不受其他请求干扰。具体来说,在每次尝试修改某项产品的库存之前都会先申请对应的分布式;一旦完成相应改动之后便会立即释放掉它以便让后续待处理的任务继续运行下去。 #### 3. **最佳实践建议** - **合理配置参数** 设置合适的租约时间和最大等待时长非常重要。过短可能导致事务未结束就被强制回收而失败重试增加负担;太长则容易造成死或者长时间阻塞其它正常流程。 - **异常情况下的安全措施** 如果程序崩溃或网络分区等原因致使客户端丢失连接,则应该依赖于 Redis 自身 TTL 特性自动清除遗留下来的无主定状态以防永久卡住整个系统运作链条[^2]。 - **监控与报警机制建立** 对频繁发生争用现象的地方加强实时跟踪记录分析找出瓶颈所在位置进而优化算法设计少不必要的冲突概率提升整体性能现水平。 #### 4. **总结** 综上所述,采用 Redisson 分布式技术确实能够在很大程度缓解甚至彻底杜绝由于多实例同时写入造成的库存超卖风险问题出现的可能性。然而值得注意的是这仅仅只是其中一个环节而已还需要配合良好的架构规划以及其他配套措施共同协作才能够真正意义上达到理想效果目标^。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值