乐观锁和悲观锁区别:数据库并发控制的两种思路(进阶教程)

在处理多用户同时操作数据的场景中,比如电商平台抢购商品、多人协作编辑表格,数据冲突是常遇到的问题。为了解决这类问题,数据库设计了不同的机制,其中最常见的就是乐观锁和悲观锁。它们名字听起来像是哲学概念,其实各有适用场景。

悲观锁:假设冲突总会发生

悲观锁的核心思想是“防患于未然”。它认为只要多个用户同时操作同一条数据,就极有可能产生冲突,所以干脆一开始就加锁,不让别人动。

比如你在编辑一个共享表格中的某一行数据,系统使用悲观锁的话,就会在你打开编辑的那一刻就把这行“锁住”,其他人只能看,不能改,直到你保存并释放锁。

在数据库中,通常用 SELECT ... FOR UPDATE 来实现悲观锁:

START TRANSACTION;
<span class="kwd">SELECT</span> * <span class="kwd">FROM</span> products <span class="kwd">WHERE</span> id = 1 <span class="kwd">FOR</span> UPDATE;
<span class="kwd">UPDATE</span> products <span class="kwd">SET</span> stock = stock - 1 <span class="kwd">WHERE</span> id = 1;
COMMIT;

在这段代码执行期间,其他事务如果也想对 id=1 的记录加锁,就得排队等着。

乐观锁:相信世界是美好的

乐观锁则相反,它默认大家不会撞车,允许所有人先读数据,只在提交时检查有没有人改过。如果有,就告诉你“不好意思,别人已经改了,请重新操作”。

这种机制更像网盘里的协同编辑——你们可以同时打开同一个文件,但当你保存时,系统发现版本不一致,就会弹出冲突提示。

实现上,乐观锁通常通过版本号或时间戳字段来完成。每次更新数据时,都检查版本是否匹配:

<span class="kwd">UPDATE</span> products
<span class="kwd">SET</span> stock = stock - 1, version = version + 1
<span class="kwd">WHERE</span> id = 1 <span class="kwd">AND</span> version = 10;

如果这条 SQL 影响的行数为 0,说明版本对不上,意味着数据已被他人修改,当前操作需要重试或提示失败。

怎么选?看场景

悲观锁适合写操作频繁、冲突概率高的场景,比如库存扣减、银行转账。虽然安全,但容易造成阻塞,影响并发性能。

乐观锁适合读多写少的场景,比如文章点赞、表单填写。它不加锁,响应快,但在高并发写入时可能频繁冲突,导致重试次数增多。

举个例子:抢购活动刚开始,上千人同时点“购买”,这时用悲观锁可能会让大多数人卡在等待锁的路上;而用乐观锁,虽然很多人提交时会失败,但系统整体吞吐更高,只要前端友好提示“再试一次”,体验也不差。

选择哪种锁,本质是在“安全性”和“效率”之间做权衡。理解它们的区别,才能在设计系统时少踩坑。