mysql如何理解乐观锁和悲观锁的区别与实现方式

来源:Webpack教程作者:吴凌云头衔:网络博主
导读:本期聚焦于小伙伴创作的《mysql如何理解乐观锁和悲观锁的区别与实现方式》,敬请观看详情。下单扣减库存时,如果两个请求同时读到剩余十件并都提交,会不会超卖。这个问题的答案取决于锁的选择。乐观锁假设并发冲突很少,更新时通过版本号或条件判断校验数据是否被改动,典型写法是update语句带where version等于旧值。悲观锁则认为冲突频繁,在读取时就加锁阻止他人修改,比如select for update借助数据库行锁实现。二者在吞吐量、阻塞风险和实现复杂度上差异明显。理解它们,关键要看业务写冲突概率与一致性要求,而不是盲目套用某种机制。

在mysql的并发控制中,乐观锁和悲观锁是两种截然不同的思路。它们并不是mysql内置的某一种特定锁类型,而是基于mysql提供的底层能力,如事务、行锁、版本字段等,在应用层或sql层面构建出的并发控制策略。明确二者的本质差异,可以帮助我们针对不同的业务场景选择合适的方案。

mysql如何理解乐观锁和悲观锁的区别与实现方式

什么是悲观锁

悲观锁的核心假设是:数据在并发访问时极有可能被其他事务修改,因此必须在操作开始前就先锁定资源,防止别人动它。在mysql里,悲观锁通常依赖innodb存储引擎的行级锁来实现,最典型的用法就是在事务中通过select ... for update语句给目标行加排他锁。

当一个事务执行了select ... for update,其他事务如果也想对这些行加锁或者修改它们,就会被阻塞,直到前一个事务提交或回滚释放锁。这种方式能有效避免丢失更新问题,但代价是并发性能下降,且在锁等待超时或死锁时需要进行额外处理。下面的示例展示了在扣减库存场景中如何使用悲观锁:

start transaction;
-- 对商品id为1001的行加排他锁
select stock from product where id = 1001 for update;
-- 假设查到stock为10,进行扣减
update product set stock = stock - 1 where id = 1001;
commit;

上面的代码在事务内先锁定行,再读取并修改,保证同一时间只有一个事务能操作这条记录。如果另一个事务也来执行同样的select ... for update,它会被阻塞直到当前事务结束。

悲观锁的优点是逻辑直观、数据一致性最强,适合写冲突频繁的核心业务,比如账户余额变更。缺点也很明显:长事务会导致锁持有时间过久,拖垮系统吞吐量;另外使用不当容易引发死锁,需要配合合理的索引和事务设计。

什么是乐观锁

乐观锁的假设正好相反:认为大部分情况下不会发生并发冲突,所以不在读取时加锁,而是在提交更新时检查数据在此期间有没有被别人改过。在mysql中,最常见的是通过增加版本号字段或时间戳字段来实现,也可以在更新时用业务条件充当校验。

典型的实现是在表里加一个version字段,每次更新时要求version等于之前读到的值,同时把version加一。如果更新返回影响行数为零,就说明版本不匹配,被人抢先修改了,此时应用层可以重试或报错。示例如下:

-- 先查询,假设读到id=1001, stock=10, version=3
select stock, version from product where id = 1001;
-- 尝试扣减,仅当版本未变时才成功
update product
set stock = stock - 1, version = version + 1
where id = 1001 and version = 3;

如果另外一个事务已经把version改成了4,那么这句update就匹配不到行,影响行数为零,应用就能感知到冲突。相比悲观锁,它完全不阻塞读,也不会产生数据库层面的行锁等待。

除了版本号,还可以直接用库存数作为条件,例如where id=1001 and stock>=1,但这只防超卖,不能防止基于旧值的复杂计算被覆盖。乐观锁适合读多写少、冲突概率低的场景,如商品详情浏览量统计、配置项修改。其缺点是如果冲突频繁,重试会带来额外开销,且编写重试逻辑时要注意幂等性。

二者核心差异对比

从实现机制上看,悲观锁依赖数据库锁机制,在读取阶段就排他;乐观锁依赖应用层版本校验,在写入阶段才冲突检测。这导致它们在性能特征上完全不同。我们可以通过一张表来直观比较:

对比维度悲观锁乐观锁
加锁时机读取时更新时校验
是否阻塞其他事务
适用冲突频率
实现复杂度低,靠sql中,需版本字段与重试
典型语句select for updateupdate where version

从表中可以看出,选哪种并不是绝对的好坏问题。如果业务是抢购秒杀且库存极少,用悲观锁更稳妥;如果是后台配置编辑,一天改不了几次,乐观锁明显更轻量。

还要注意,乐观锁在mysql中并不是真的无锁,它只是把并发控制从数据库锁转移到了where条件与版本比对上。在高并发写同一个热点行时,大量update失败重试也会给数据库造成压力,此时可能需要结合队列或分桶扣减等架构手段。

在项目中如何选择

做技术选型时,先评估写冲突概率和一致性要求。若每次操作都必须基于最新且独占的数据,比如转账、充值,优先用悲观锁,并尽量缩小事务范围,避免锁住无关行。若只是偶尔修改且可以接受重试,比如用户修改昵称、文章点赞数,乐观锁更合适。

实际开发中也可以混合使用。例如先在应用层用redis做库存预扣(乐观计数),真正落库时再用mysql悲观锁兜底,既抗住流量又保证最终准确。无论哪种方式,都要通过压测验证,在日志中记录锁等待或更新失败次数,方便后续调优。

理解mysql的乐观锁与悲观锁,重点不在于背诵定义,而在于看清它们背后对并发冲突的假设,以及这种假设如何转化为具体的sql写法和系统表现。把业务特征与锁特征对齐,才能写出既安全又高效的数据库代码。

mysqloptimistic_lockpessimistic_lock修改时间:2026-08-09 21:51:35

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。