🌋 图文并茂的带你彻底理解悲观锁与乐观锁

阿轩的BUG
文章191
评论56
标签101
站点日志
作者总数:1位
置顶文章:0篇
标签总数:101条
文章总数:196篇
评论总数:56条
微语总数:201条
运行天数:2319天
最近更新:2026-07-28
文章标签
最新评论
Sherlock
2025-08-21
不是应该打包成apk文件吗,怎么突然就运行编译了,下一步呢
老金er
2025-05-25
人生设计非常必要,在信息化发达的今天,周围大部分人依然只能过着人生工程模式生活,长时间生活洗礼后,尽管“人生工程”已经不再适合很多人,他们也无法做出任何改变,人生设计给我们提供了新的思路,值得尝试
赏帮赚
2025-05-25
职场太多的尔虞我诈
三笑
2024-08-16
emlog6.0.1可以升级支持吗?
pony
2024-07-08
这篇文章提供了一种新的人生规划方法——"人生设计",它借鉴了产品设计的理念,鼓励人们以创造性和探索性的方式思考和规划自己的生活。以下是对这篇文章的几个评价维度: 创新性:将产品设计的理念应用于人生规划是一个新颖的视角,为人们提供了一种跳出传统生活轨迹,探索个性化生活路径的方法。 实用性:文章不仅提出了理念,还详细介绍了具体的实施步骤,包括自我评估、制定计划、原型设计和实践选择等,具有很强的操作性。 启发性:通过强调好奇心、不断尝试、重新定义问题等心态,文章鼓励人们打破常规,勇于探索未知,这对于激发个人潜能和创造力具有积极作用。 适应性:"人生设计"理念适用于不同人生阶段和不同背景的人,无论是希望从头开始的人,还是已经取得一定成就但希望探索新方向的人,都可以从中获得启发。 情感共鸣:文章对许多人对传统生活轨迹的不满和对改变的渴望进行了深刻的洞察,能够引起读者的共鸣。 可持续性:人生设计强调的是一个持续迭代和试错的过程,这与现实生活的发展规律相吻合,有助于人们在不断变化的环境中做出适应性调整。 教育意义:作为一种人生规划方法,"人生设计"可以作为教育的一部分,帮助年轻人更早地学会自我探索和规划,为未来的生活和职业发展打下基础。 局限性:尽管"人生设计"提供了一种新的思考框架,但它可能需要个人具备一定的自我认知和反思能力,对于一些缺乏自我探索经验的人来说,可能需要额外的指导和支持。 总体来说,这篇文章提供了一种有价值的人生规划方法,对于那些渴望改变、寻求个性化生活的人来说,是一种有益的参考和指导。
首页实用干货 🌋

这是一篇介绍悲观锁和乐观锁的入门文章。旨在让那些不了解悲观锁和乐观锁的小白们弄清楚什么是悲观锁,什么是乐观锁。不同于其他文章,本文会配上相应的图解让大家更容易理解。通过该文,你会学习到如下的知识。

锁(Lock)

在介绍悲观锁和乐观锁之前,让我们看一下什么是锁。

锁,在我们生活中随处可见,我们的门上有锁,我们存钱的保险柜上有锁,是用来保护我们财产安全的。

程序中也有锁,当多个线程修改共享变量时,我们可以给修改操作上锁(syncronized)。

当多个用户修改表中同一数据时,我们可以给该行数据上锁(行锁)。因此, 锁其实是在并发下控制多个操作的顺序执行,以此来保证数据安全的变动。

并且,锁是一种保证数据安全的机制和手段,而并不是特定于某项技术的。悲观锁和乐观锁亦是如此。本篇介绍的悲观锁和乐观锁是基于数据库层面的。

悲观锁

悲观锁(Pessimistic Concurrency Control),第一眼看到它,相信每个人都会想到这是一个悲观的锁。没错,它就是一个悲观的锁。

那这个悲观体现在什么地方呢?悲观是我们人类一种消极的情绪,对应到锁的悲观情绪,悲观锁认为被它保护的数据是极其不安全的,每时每刻都有可能变动,一个事务拿到悲观锁后(可以理解为一个用户),其他任何事务都不能对该数据进行修改,只能等待锁被释放才可以执行。

数据库中的行锁,表锁,读锁,写锁,以及syncronized实现的锁均为悲观锁。

这里再介绍一下什么是数据库的表锁和行锁,以免有的同学对后面悲观锁的实现看不明白。

我们经常使用的数据库是mysql,mysql中最常用的引擎是Innodb,Innodb默认使用的是行锁。而行锁是基于索引的,因此要想加上行锁,在加锁时必须命中索引,否则将使用表锁。

乐观锁

与悲观相对应,乐观是我们人类一种积极的情绪。乐观锁(Optimistic Concurrency Control) 的“乐观情绪”体现在,它认为数据的变动不会太频繁。因此,它允许多个事务同时对数据进行变动。

但是,乐观不代表不负责,那么怎么去负责多个事务顺序对数据进行修改呢?

乐观锁通常是通过在表中增加一个版本(version)或时间戳(timestamp)来实现,其中,版本最为常用。

事务在从数据库中取数据时,会将该数据的版本也取出来(v1),当事务对数据变动完毕想要将其更新到表中时,会将之前取出的版本v1与数据中最新的版本v2相对比,如果v1=v2,那么说明在数据变动期间,没有其他事务对数据进行修改,此时,就允许事务对表中的数据进行修改,并且修改时version会加1,以此来表明数据已被变动。

如果,v1不等于v2,那么说明数据变动期间,数据被其他事务改动了,此时不允许数据更新到表中,一般的处理办法是通知用户让其重新操作。不同于悲观锁,乐观锁是人为控制的。

如何实现

经过上面的学习,我们知道悲观锁和乐观锁是用来控制并发下数据的顺序变动问题的。那么我们就模拟一个需要加锁的场景,来看不加锁会出什么问题,并且怎么利用悲观锁和乐观锁去解决。

场景:A和B用户最近都想吃猪肉脯,于是他们打开了购物网站,并且找到了同一家卖猪肉脯的>店铺。下面是这个店铺的商品表goods结构和表中的数据。

id name num
1 猪肉脯 1
2 牛肉干 1

从表中可以看到猪肉脯目前的数量只有1个了。在不加锁的情况下,如果A,B同时下单,就有可能导致超卖。

悲观锁解决

利用悲观锁的解决思路是,我们认为数据修改产生冲突的概率比较大,所以在更新之前,我们显示的对要修改的记录进行加锁,直到自己修改完再释放锁。加锁期间只有自己可以进行读写,其他事务只能读不能写。

A下单前先给猪肉脯这行数据(id=1)加上悲观锁(行锁)。此时这行数据只能A来操作,也就是只有A能买。B想买就必须一直等待。

当A买好后,B再想去买的时候会发现数量已经为0,那么B看到后就会放弃购买。

那么如何给猪肉脯也就是id=1这条数据加上悲观锁锁呢?我们可以通过以下语句给id=1的这行数据加上悲观锁

select num from goods where id = 1 for update;

下面是悲观锁的加锁图解

我们通过开启mysql的两个会话,也就是两个命令行来演示。
1、事务A执行命令给id=1的数据上悲观锁准备更新数据

这里之所以要以begin开始,是因为mysql是自提交的,所以要以begin开启事务,否则所有修改将被mysql自动提交。

2、事务B也去给id=1的数据上悲观锁准备更新数据

我们可以看到此时事务B再一直等待A释放锁。如果A长期不释放锁,那么最终事务B将会报错,这有兴趣的可以去尝试一下。

3、接着我们让事务A执行命令去修改数据,让猪肉脯的数量减一,然后查看修改后的数据,最后commit,结束事务。

我们可以看到,此时最后一个猪肉脯被A买走,只剩0个了。

4、当事务A执行完第3步后,我们看事务B中出现了什么

我们看到由于事务A释放了锁,事务B就结束了等待,拿到了锁,但是数据此时变成了0,那么B看到后就知道被买走了,就会放弃购买。

通过悲观锁,我们解决了猪肉脯购买的问题。

乐观锁解决

下面,我们利用乐观锁来解决该问题。上面乐观锁的介绍中,我们提到了,乐观锁是通过版本号version来实现的。所以,我们需要给goods表加上version字段,表变动后的结构如下:

id name num version
1 猪肉脯 1 0
2 牛肉干 1 0

使用乐观锁的解决思路是,我们认为数据修改产生冲突的概率并不大,多个事务在修改数据的之前先查出版本号,在修改时把当前版本号作为修改条件,只会有一个事务可以修改成功,其他事务则会失败。

A和B同时将猪肉脯(id=1下面都说是id=1)的数据查出来,然后A先买,A将id=1和version=0作为条件进行数据更新,即将数量-1,并且将版本号+1。

此时版本号变为1。A此时就完成了商品的购买。最后B开始买,B也将id=1和version=0作为条件进行数据更新,但是更新完后,发现更新的数据行数为0,此时就说明已经有人改动过数据,此时就应该提示用户重新查看最新数据购买。

下面是乐观锁的加锁图解

我们还是通过开启mysql的两个会话,也就是两个命令行来演示。

1、事务A执行查询命令,事务B执行查询命令,因为两者查询的结果相同,所以下面我只列出一个截图。

此时A和B均获取到相同的数据

2、事务A进行购买更新数据,然后再查询更新后的数据。

我们可以看到事务A成功更新了数据和版本号。

事务B再进行购买更新数据,然后我们看影响行数和更新后的数据

可以看到最终修改行数为0,数据没有改变。此时就需要我们告知用户重新处理。

优缺点

下面我们介绍下乐观锁和悲观锁的优缺点以便我们分析他们的应用场景,这里我只分析最重要的优缺点,也是我们要记住的。

悲观锁

  • 优点:悲观锁利用数据库中的锁机制来实现数据变化的顺序执行,这是最有效的办法
  • 缺点:一个事务用悲观锁对数据加锁之后,其他事务将不能对加锁的数据进行除了查询以外的所有操作,如果该事务执行时间很长,那么其他事务将一直等待,那势必影响我们系统的吞吐量

乐观锁

  • 优点:乐观锁不在数据库上加锁,任何事务都可以对数据进行操作,在更新时才进行校验,这样就避免了悲观锁造成的吞吐量下降的劣势
  • 缺点:乐观锁因为是通过我们人为实现的,它仅仅适用于我们自己业务中,如果有外来事务插入,那么就可能发生错误。

应用场景

  • 悲观锁:因为悲观锁会影响系统吞吐的性能,所以适合应用在写为居多的场景下。
  • 乐观锁:因为乐观锁就是为了避免悲观锁的弊端出现的,所以适合应用在读为居多的场景下。
上一篇
😶 select for update加了行锁还是表锁?
下一篇
💥 数据库锁的12连问,你顶得住嘛?