浅析乐观锁与悲观锁

悲观锁

当我们要对数据库中的一条数据进行修改的时候,为了避免同时被其他人修改,最好的办法就是直接对该数据进行加锁以防止并发。这种借助数据库锁机制在修改数据之前锁定,再修改的方式被称为悲观并发控制(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。
本帖由系统于 4年前 自动加精
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 10

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

4年前 评论

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

4年前 评论

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

4年前 评论

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

4年前 评论

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

4年前 评论

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

4年前 评论

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

4年前 评论

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

4年前 评论

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

4年前 评论

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

4年前 评论

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