悲观锁
早睡蛋
# 悲观锁的范围
悲观锁:select ... for update
我们模拟一个例子,首先插入新的表数据:
//首先执行如下SQL语句,操作的行数显示为2行,在事务提交之前,这两条数据是被锁住了
update db_stock set count = count - 1 where product_code='1001' and count >= 1;
//然后我们对id=3这条数据进行操作,看看能不能进行操作
update db_stock set count = count - 1 where id = 3;
//执行回车,被阻塞了,然后我们提交或者回滚第一条事务
rollback;
//可以看到事务二立刻被放行了
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
由此可见,这种SQL语句是一种表级锁,在一般的实际业务中,是非常不建议使用这种表级锁的。
# 在MySQL悲观锁中使用行级锁
悲观锁使用行级锁需要以下几个条件:
- 锁的查询或者更新条件必须是索引字段
在表中给product_code添加索引'idx_pc',然后执行以下流程:
//事务一执行更新操作,但不提交事务
update db_stock set count = count - 1 where product_code='1001' and count >= 1;
//事务二执行更新操作
update db_stock set count = count - 1 where id = 3;
//事务二可以正常执行,没有被阻塞
1
2
3
4
5
6
7
2
3
4
5
6
7
- 查询或更新条件必须是具体值
//事务一执行更新操作,但条件改为like模糊判断,以001结尾的数据
update db_stock set count = count - 1 where product_code like'%001' and count >= 1;
//或者事务一条件改为 !=判断
update db_stock set count = count - 1 where product_code !='1002' and count >= 1;
//开启事务二,也进行更新操作
update db_stock set count = count - 1 where id = 3;
//事务二被阻塞了,因为索引失效了
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 悲观锁使用select for update减库存
使用方法:select ... for update
注意:在此之前,product_code字段已经是加了索引的。
//事务一执行
select * from db_stock where product_code='1001' for update
//事务二执行减库存
update db_stock set count = count - 1 where id = 1;
1
2
3
4
2
3
4
加了'for update'语句后,我们可以发现,事务二的写操作的会被阻塞的,因为事务一用了悲观锁的行级锁。这个时候,只有事务一被提交或者回滚了,锁才会被释放。
代码实现:
在Mapper接口创建方法:
@Select("select * from db_stock where product_code= #{productCode} for update")
List<Stock> queryStock(@Param("productCode") String productCode);
1
2
2
在Service层编写业务代码:
@Autowired
private StockMapper stockMapper;
@Transactional
public void deduct() {
//1.查询库存信息,并且锁定库存信息
List<Stock> stocks = stockMapper.queryStock("1001");
//这里取第一个仓库库存来计算
Stock stock = stocks.get(0);
//2.判断库存是否充足
if (stock != null && stock.getCount() > 0) {
//3.扣减库存
stock.setCount(stock.getCount() - 1);
stockMapper.updateById(stock);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
在集群模式下,通过压力测试,并发问题没有出现。
悲观锁的性能不是非常高,因为有多条SQL语句执行,锁的粒度变大了
# 悲观锁select for update存在的问题
优点:
- 解决了一个SQL语句在同一个商品有多条库存记录下不够灵活的问题
- 解决了一个SQL语句无法记录库存前后变化的状态的问题
缺点:
- 性能虽然比JVM本地锁高一点,但是比起一条SQL语句的性能,差的非常远
- 死锁问题,对多条数据加锁时,加锁顺序要一致
- 库存操作要统一,select for update是减库存时的操作,但是在实际业务中,还有出库入库等等其他操作,而且也是频繁的操作,也会被影响到,所以要全部都使用统一语句