利用mysql特性
隔离级别
首先数据库是5.6.70,默认隔离级别为可重复读(Repeatable Read) 数据库隔离级别 可重复读 理解
事务开始后直到结束,读取的永远都是同一个值。
行锁
update默认有行锁 mysql update 锁行还是锁表
项目需求背景
原先签到活动 奖品实例和优惠券实例 生成消费逻辑
- 原先在活动审核成功后 奖品实例和优惠券实例 由后台生成
- 用户签到请求接口这边 对奖品实例和优惠券实例进行绑定,库存减一,使用的是先查询,在代码上减一,再去update
现需求 为签到活动追加奖品实例和优惠券实例
- 后台生成的时候,接口这边就进行消费,刚生成的优惠券,就被绑定了,这是不好的,可能存在未知问题,不过因为事务的存在,直到事务结束,生成的时候都不会被读取到,所以不用担心
- 库存并发问题:后台生成完实例后,对原库存进行增加,但是接口也可能进行减库存
解决办法
方法一:不可取
两边都加redis锁 ,分别增加,最后在更新,由于锁的存在,只有一条语句在操作此库存
下面是伪代码
后台事务A{
redis锁A{
select stock;
stock=stock+N;
update stock=xx where id=1;
}
}
接口事务B{
redis锁A{
select stock;
stock=stock-1;
update stock=xx where id=1;
}
}
想得很好,但是仔细再想想却发现有漏洞
- 后台事务A 的 redis锁A 执行完瞬间,事务还未提交
- 接口事务B的redis锁A开始执行,这时候查询出的库存,并不是 后台事务A 更新后最新的
- 这时候 后台事务A 也执行完了,库存更改了
- 接口事务B 执行到 update,就会把后台事务A更新的库存给覆盖掉,就会导致数据脏掉
方法二:可取
利用上面讲的mysql update行锁特性便可以,同样是使用update,却不用在代码里查询出来在增删
后台 直接加,由于id为索引会锁行,而不是锁表
UPDATE stockTable a SET a.currentInventory = a.currentInventory + N WHERE a.id = 1
接口,判断库存大于0,直接减1,代码这边根据更新的行数,判断是否领取成功即可
UPDATE stockTable a SET a.currentInventory = a.currentInventory - 1 WHERE a.id = 1 AND a.currentInventory > 0
这样子非常方便简单,连分布式锁都不用加。