背景:一个定时任务(多实例),取表 A 中状态为“初始化”的记录,然后取表 B 中对应记录,B 库存-A 消费存在两种情况。若 B-A=0,AB 记录都职位“完成”状态;若 B-A>0,AB 记录都职位“完成”状态后,再生成一条新的 B 记录,库存字段值=B-A ;
正常情况一:AB 各一条记录,库存与消费相等,AB 完成; 正常情况二:AB 各一条记录,库存大于消费,AB 完成,新生成一条库存记录 B ; 正常情况三:库存 B 一条记录,值为 8,消费 A1 、A2 两条记录,值都为 4 。首先 A1 与 B 进行操作,8-4=4>0,A1 和 B 完成,然后生成一条新 B,值为 4,然后 A2 和新 B 操作后全部完成。
异常情况:根据正常情况三,A1 与 A2 先后执行没问题,但是因为是多实例,A1 和 A2 可能同时执行,同时查询到 B 那一条记录,然后俩都 8-4=4>0,最后生成俩新 B 记录
理想结果:B 那条记录能不能加个锁或其它手段控制一下(表数据量大,肯定不能锁整个表),让 A1 和 A2 先后顺序执行,避免同时执行
注:业务比较复杂,并非只是库存-消费者一个小逻辑,此处简化问题了
1
AngryPanda 2020-06-21 10:26:47 +08:00
内存锁,lockName = B 表的 ID
|
2
AngryPanda 2020-06-21 10:28:05 +08:00
乐观锁: UPDATE/INSERT XXX WHERE B.quantity = 8
|
3
AngryPanda 2020-06-21 10:30:43 +08:00
悲观锁:select xxx for update
|
4
AngryPanda 2020-06-21 10:35:33 +08:00
其实我觉得设计上稍微有点奇怪了:B > A 库存表为啥需要新生成记录呢?
|
5
duanzs OP @AngryPanda 这只是一个简化描述,其实业务上远比描述的复杂
|
6
peyppicp 2020-06-21 11:03:05 +08:00
想顺序就 3 楼悲观锁,b 表里如果有唯一索引的话,select for update 行锁锁住,然后再做业务就行了,如果有并发也会在这行锁这里等待的。
|
7
PopRain 2020-06-21 14:49:37 +08:00 1
update 库存表 set 库存=4 where PK=xx and 库存=8 肯定有一个 UPDATE 不成功的。。。。 (一般关键的更新,都要加上控制字段(或者说版本字段),例子中,原来的库存量就是“控制字段”,肯定要保证更新的是你原来的数据,这个都是开发 ERP 应用的常识。
现在很多人都是根据主键去 UPDATE,当然有问题了(估计是某些 ORM 自身的限制,或者对 ORM 了解不深) |
8
crclz 2020-06-21 15:51:28 +08:00
你这个是没有任何问题的。因为,你会将旧的 B 置为“完成”,相当于给 B 上了一个 X 锁,所以是没问题的。
|
9
crclz 2020-06-21 15:55:22 +08:00
同时,如果你的两个事务对 B 先读取的时候没有 FOR UPDATE,那么实际情况就会构成死锁,或者等待。当然,死锁是没有任何问题的,只不过会牺牲一个事务。整个流程是没有问题的,我只是解释一下内部运行过程。
|