在一人一单问题里,为什么加了事务还是会出现一人下多单呢?
本质的原因是,我们使用Java的对象锁,可以保证临界区只有一个线程访问,但是这和SpringBoot里加@Transactional
注解不是等价的。数据库里的事务保证的是要么全部完成要么全部不起作用。在开启事务的时候,会有不同的数据库锁保证并发性。但是并不能保证我们执行逻辑的正确性,见下面的例子。
@Transactional //事务 涉及两张表,需要事务
public void createVoucherOrder(VoucherOrder voucherOrder) {
// 一人一单,检查用户是否下过单
Long userId = voucherOrder.getUserId();
int count = query().eq("user_id", userId).eq("voucher_id", voucherOrder).count();
if (count > 0) {
log.error("用户已经购买过");
return;
}
// 扣减库存
boolean success = seckillVoucherService.update()
.setSql("stock=stock-1")
.eq("voucher_id", voucherOrder.getVoucherId())
.gt("stock", 0)
.update();
if (!success) {
log.error("库存不足");
return;
}
save(voucherOrder);
}
开启事务以后,对于第一个查是否下过单的操作,由于是快照读没有加锁,多个线程读到都是满足下单条件的。
第二步update就是当前读了,走了主键索引,会触发行级锁。
可以看出,加锁的类型是X型的记录锁,对那一行加了锁。
然后有第二个线程要执行同样的操作是会阻塞,但是不会下单失败。等第一个线程提交了,第二个线程也会提交。所以不能够解决一人一单的问题。
所以本质就是第一个操作没加锁,除非直接加表级锁,但是效率太低了。
所以可以用Java的对象锁来解决这个问题,但是不是简单的加sychronized
就行了,可以详见这篇。为什么加了锁还有事务问题