举例说明 MySQL 乐观锁和悲观锁的使用场景。
未标记
考查重点:冲突频率与等待、重试代价。低冲突文档version条件更新是乐观,余额等短事务先FOR UPDATE再改是悲观,按重试/等待代价选
本题目录
参考答案
核心回答
MySQL 中,乐观锁适合冲突较少、允许检测冲突后重试或提示用户的更新,常用版本号或业务状态作为更新条件;悲观锁适合一段短事务里需要先读后改、并保护多步判断的场景,通常使用 InnoDB 的锁定读,让竞争者等待。
例如,后台编辑商品信息时,可在保存时检查读取的版本号,版本变化就提示冲突,避免覆盖别人修改。账户内部转账若需要读取两条余额、校验并更新,可以在同一事务中按固定顺序锁定账户,完成后提交。这里是策略示例,具体选择还取决于冲突率、事务长度和失败处理成本。
乐观锁怎样保护一次修改
假设两个人都读取商品版本 7。第一个请求以“版本仍为 7”为条件更新内容,并把版本改为 8;第二个请求再按版本 7 更新时匹配不到记录,就应把它识别为冲突,重新读取并决定合并、重试或返回冲突提示。
版本检查和更新必须是同一条原子数据库操作,先查版本再无条件写回仍会丢失更新。还要检查受影响行数,避免把未匹配、记录不存在或业务条件失败一律当作成功。数据库执行这类更新仍可能获取行锁,所以“乐观锁”不等于底层完全没有锁。
低冲突时,它减少了从读取到提交之间的长期占锁;冲突频繁时,大量重读与重算会浪费资源。重试还需要上限,不能遇到冲突就无限循环。
悲观锁怎样保护多步判断
在显式事务中读取需要修改的记录并加锁,校验约束、执行修改,再提交或回滚。竞争事务请求不兼容的锁时需要等待,或按配置快速返回失败。
账户转账应按账户 ID 等稳定顺序获取两边的锁,减少相反顺序带来的死锁;事务里不要夹带用户输入等待和慢速外部调用。查询要命中适当索引,实际锁范围还受索引、查询条件和隔离级别影响,锁范围应结合索引与实际扫描范围判断,而不能只看 SQL 的表面条件。
进一步追问
- 扣库存必须先查再加锁吗? 不一定。如果一个带库存条件的原子更新已能表达约束,就可以直接完成检查与扣减;需要同时维护订单、流水等状态时再组织事务。
- 加锁能保证幂等吗? 加锁只控制并发。锁释放后重复请求仍能再次到达,还需业务唯一键或状态机识别同一笔操作。
- 发生死锁怎么办? 先回滚并识别可重试事务,有限重试;同时查看死锁记录,检查加锁顺序、索引扫描范围和长事务。不要把数据库主动回滚当作业务已提交。
参考资料
- 国内:JavaGuide / Snailclimb:接口幂等性设计详解:借鉴版本号条件更新与锁定读的对比,解释正文中乐观锁和悲观锁在业务更新中的适用场景。
- 国内:JavaGuide / Snailclimb:MySQL 事务隔离级别详解:快照读和当前读的区别说明普通查询为何不能代替需要保护后续修改的锁定读。
- 国内:小林 coding:MySQL 有哪些锁?:参考行锁和事务的说明,解释正文中锁范围与缩短持锁事务的要求。
- 国外:MySQL / Oracle:Locking Reads:锁定读与计数器示例说明悲观并发控制如何保护先读后改,以及事务结束时如何释放锁。
- 国外:MySQL / Oracle:Deadlocks in InnoDB:依据加锁顺序与死锁受害者回滚,补充正文中悲观锁失败后的回滚、排查与有限事务重试。

