浅谈乐观锁与悲观锁

面试题:当一个商品只有10个库存,而有100并发进行抢单的时候,你该怎么保证不会超卖。

我认为有多种方式都可以实现,比如缓存控制数量再用消息队列来处理真实的订单等;
但今天我选择用悲观锁乐观锁来延伸解答这个问题。

悲观锁:在对任意记录进行修改前,先尝试为该记录加上排他锁(exclusive locking)。如果加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常。 具体响应方式由开发者根据实际需要决定。如果成功加锁,那么就可以对记录做修改,事务完成后就会解锁了。其间如果有其他对该记录做修改或加排他锁的操作,都会等待我们解锁或直接抛出异常。

悲观锁使用的是数据库提供的锁,具体的锁还需要结合数据库、引擎考虑(往后会继续讨论下去,今天先解题)

乐观锁:乐观锁并不会使用数据库提供的锁机制。一般的实现乐观锁的方式就是记录数据版本。
数据版本,为数据增加的一个版本标识。当读取数据时,将版本标识的值一同读出,数据每更新一次,同时对版本标识进行更新。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的版本标识进行比对,如果数据库表当前版本号与第一次取出来的版本标识值相等,则予以更新,否则认为是过期数据。

乐观锁是一种思想,依赖于我们自己定义的字段来控制是否成功。

显然地从这道题上来看,我们会使用乐观锁。悲观锁会使每次查询修改均加锁,效率上带来开销,而且还会出现死锁的可能性。

乐观锁具体该怎么做呢?
其实乐观锁就是需要我们用现有的字段或额外的字段来控制并发下的“超卖”行为。

1
2
3
4
5
#先把商品查出来,此时商品剩余N个
> select * from goods where id = #{id}

#更新数量的时候,把查询出来的数量也作为条件来查询。这样当并发环境下,这条语句是有可能出现Affected rows:0的情况的。
> update goods set number=number-1 where id =#{id} and number=#{number}

这样我们就可以控制到“超卖”行为了。