浅析乐观锁与悲观锁

悲观锁

当我们要对数据库中的一条数据进行修改的时候,为了避免同时被其他人修改,最好的办法就是直接对该数据进行加锁以防止并发。这种借助数据库锁机制在修改数据之前锁定,再修改的方式被称为悲观并发控制(PCC)。

之所以叫做悲观锁,是因为抱有悲观的态度去修改数据的并发控制方式,认为数据并发修改的概率比较大,所以需要在修改之前先加锁。

悲观并发控制实际上是“先取锁,再访问”的保守策略,为数据处理的安全提供了保证。

简单实践乐观锁与悲观锁

在效率上,处理加锁的机制会让数据库产生额外的开销,还会有死锁的可能性。降低并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数据。

悲观锁的实现方式:悲观锁的实现,依靠数据库提供的锁机制。在数据库中,悲观锁的流程如下:

  • 在对数据修改前,尝试增加排他锁。
  • 加锁失败,意味着数据正在被修改,进行等待或者抛出异常。
  • 加锁成功,对数据进行修改,提交事务,锁释放。
  • 如果我们加锁成功,有其他线程对该数据进行操作或者加排他锁的操作,只能等待或者抛出异常。

乐观锁

乐观锁是相对悲观锁而言的,乐观锁假设数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测。

相对于悲观锁,在数据库进行处理的时候,乐观锁不会使用数据库提供的锁机制,一般是增加 version 参数,记录数据版本

简单实践乐观锁与悲观锁

乐观并发控制相信事务之间的数据竞争概率非常小,因此尽可能直接操作,提交的时候才去锁定,不会产生任何锁和死锁。

上手试一试

基于 MySQL InnoDB 引擎

使用悲观锁

begin;
select quantity from products where id = 1 for update;
update products set quantity = 2 where id = 1;
commit;

以上,对 id 为 1 的产品进行修改,先通过 for update 的方式进行加锁,然后再修改。典型的悲观锁策略。

如果修改库存的逻辑发生并发,同一时间只有一个线程可以开启事务并获得 id = 1 的锁,其他事务必须等本次提交之后才能执行,这样可以保证数据不被其他事务修改。

使用排他锁会把数据锁住,不过需要注意一些基本的锁级别,MySQL InnoDB 默认行级锁。行级锁是基于索引的,如果一条 SQL 语句用不到索引是不会使用行级锁,会使用表级锁把整张表锁住。

使用乐观锁

select quantity from products where id = 1
update products set quantity = 2 where id = 1 and quantity = 3

先查询库存表当前库存数,然后更新的时候判断数据表对应数据的 quantity 与第一次取出来的是否一致,一致则更新,否则认为是过期数据。

这样实现有一个问题,线程 1 从数据库取出 quantity 为 3,线程 2 也取出同一条数据的 quantity,进行操作,变成了 2,然后又进行某些操作 变成了 3,此时线程 1 进行更新操作成功。但是这个过程有问题。

引入 version 参数,乐观锁每次在执行数据修改的操作,都会带上版本号,一旦版本号和数据的版本号一致就可以执行修改操作并对 version 执行 +1 操作,否则就执行失败。

这样实现也有一个问题,如果真的有高并发的时候,就只有一个线程可以修改成功,就会存在大量的失败。

如果你的应用存在超高并发,这样解决也不好,因为会总让用户感知到失败。

尝试减小乐观锁力度,最大程度提高吞吐。

update products set quantity = quantity - 1 where id = 1 and quantity - 1 > 0

使用这条 SQL 语句,在执行过程中,会在一次原子操作中查询一遍 quantity 的值,并且减去 1。

简述区别

  1. 乐观锁不是真的加锁,效率高,但是要控制好锁的力度。
  2. 悲观锁依赖数据库锁,效率低。

总结

无论是悲观锁还是乐观锁,都是人们定义出来的概念,可以认为是一种思想。

大家要记住锁机制一定要在事务中才能生效哦。

以上是我对乐观锁与悲观锁一点基础实践,希望能和大家再深入了解了解。

本作品采用《CC 协议》,转载必须注明作者和本文链接
Hello。
本帖由系统于 6年前 自动加精
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 10

update products set quantity = quantity - 1 where id = 1 and quanntity - 1 > 0, 这样难道不会出现并发时都减去1,结果应该时2个都减去1, 累积下来应该减去2; 但是实际上只减去了1, 不会有这种情况出现吗

6年前 评论

@wojianduanfa_sxm_87 update 操作,会使用写锁进行锁行或者锁表,我认为不会出现这种情况。

6年前 评论

up主,这些理论百度一大堆啊,能不能讲下laravel上怎么实现好过,我要吃粟子

6年前 评论

@儒雅竹虫 阅读一下文档的 数据库/查询构造器 章节哈

6年前 评论

@icecho 官方文档只是悲观锁,这种场景对于稍微有并发的场景,就卡爆了。乐观锁才符合,但laravel怎么实现妥当

6年前 评论

@儒雅竹虫 悲观锁对应lockForUpdate,乐观锁对应sharedLock,详细用法可以看文档

6年前 评论

@儒雅竹虫 悲观锁对应lockForUpdate,乐观锁对应sharedLock,详细用法可以看文档

6年前 评论

@linxb 不行的,高并发时,会不断的卡断后续用户的访问,我自己实现用版本控制,哪个更新快就成功,后更新者就失败。这样用户不会有等待感。

6年前 评论

@Linxb 别乱说啊.......官方文档列出的这两个方式都是悲观锁。

5年前 评论

是啊。官方文档中只有悲观锁没有乐观锁。

5年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!