本篇以如何防止库存超卖为例 ,介绍如何使用悲观锁和乐观锁,存储引擎为innodb。

并发超卖问题

销售时,先查询出id=100的商品的库存数量quantity,只要数量大于0,说明还有库存,销售后将数量减1,如下。

SELECT quantity FROM t WHERE id=100;
#判断库存大于0,则执行销售流程,否则中断业务
UPDATE t SET quantity = quantity -1 WHERE id=100;

问题:现在总共只有1件商品,有2个客户同时购买,同时查询库存时,并不会加锁。2个客户查到的数量都是1,都会执行销售流程,都会减库存,导致库存减2,变为-1,形成超卖。

悲观锁

悲观锁如何防止超卖?使用用SELECT ... FOR UPDATE,查询时加锁。第一个客户查询后,第二个客户再查询将会被阻塞,一直等到第一个客户的事务提交了,才能执行第二个客户的查询,第二个客户看的库存为0,中断业务。这种解决方式简单粗暴,但是会造成阻塞,性能不如乐观锁。

#修改查询语句
SELECT quantity FROM t WHERE id=100 FOR UPDATE;

乐观锁

不再阻塞,两个客户同时操作减库存,但是只有第一个客户能操作成功。这样做,需要有一个动态参数,如数据版本号,这里以库存量quantity为例介绍

SELECT quantity FROM t WHERE id=100;
#假设查询库存为1
UPDATE t SET quantity = quantity -1 WHERE id=100 and quantity =1;

如上sql,两个用户查询到库存为1,就把库存1 作为当前版本号。执行减少库存时,增加修改条件quantity =1,当第1个用户减少库存后,quantity变成0,那么第二个人修改条件quantity =1将不匹配,也就无法再次修改。由于没有阻塞,所以性能更高。但是并发较多时,会造成频繁的失败和重试

区别

  • 乐观锁:应用并发少的场景,认为基本不会有冲突,所以比如乐观。不会阻塞,性能较高
  • 悲观锁:应用并发多的场景,认为多半会有冲突,所以比如悲观。因为并发多时,乐观锁会执行很多无效的操作,导致资源浪费。