news 2026/3/21 10:23:14

mysql乐观锁和悲观锁

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
mysql乐观锁和悲观锁

乐观锁和悲观锁详解

面试高频 + 实战常用的并发控制手段
核心问题:什么时候锁别人,什么时候先干再说?


一、先把概念捋清楚

1. 悲观锁(Pessimistic Lock)

思想:

“我觉得你一定会和我抢,所以我先把门锁上再干活。”

  • 假设“冲突很可能发生”;
  • 在读/写数据前,先加锁,其他事务读写会被阻塞或受限;
  • 典型实现:数据库的行锁 / 表锁SELECT ... FOR UPDATE等。

2. 乐观锁(Optimistic Lock)

思想:

“我先干活,最后再确认一下期间有没有人改过,如果有就重来。”

  • 假设“冲突很少发生”;
  • 操作数据时不立即加锁,提交前检查是否有冲突;
  • 冲突检测失败:一般是重试或提示失败;
  • 典型实现:版本号(version 字段)、时间戳、CAS(Compare And Swap)

一句话总结:

  • 悲观锁:先上锁,再干活
  • 乐观锁:先干活,最后校验

二、悲观锁详解

2.1 悲观锁的典型场景

  • 并发写多、冲突概率高;
  • 更新逻辑复杂且代价大,重试成本高;
  • 强一致性要求高的场景:
    • 银行转账;
    • 库存扣减但业务逻辑复杂、流水多步更新;
    • 余额变动、核心账户系统等。

2.2 在数据库层的实现(以 MySQL InnoDB 为例)

常见悲观锁语法:

-- 排他锁(写锁):锁住选中的行,其他事务不能修改/加排他锁SELECT*FROMproductWHEREid=1FORUPDATE;-- 共享锁(读锁):允许多个事务加共享锁,但不能加排他锁SELECT*FROMproductWHEREid=1LOCKINSHAREMODE;

特点:

  • 需要在事务中使用:
BEGIN;SELECTstockFROMproductWHEREid=1FORUPDATE;-- 检查库存并更新UPDATEproductSETstock=stock-1WHEREid=1;COMMIT;
  • InnoDB 会对命中的行加行锁(可能伴随间隙锁);
  • 其他事务如果也想FOR UPDATE同一行,会被阻塞直到锁释放或超时。

2.3 悲观锁的优点

  • 一致性强:锁住资源,避免同时修改;
  • 逻辑简单:思维模型直接,容易理解;
  • 适合对数据正确性极度敏感的场景。

2.4 悲观锁的缺点

  • 并发度低:大量事务同时抢同一行时,会排长队;
  • 可能出现:
    • 死锁(多个事务交叉持有锁);
    • 锁等待时间长,影响整体性能;
  • 对长事务极不友好,事务持锁时间越长,影响越大。

三、乐观锁详解

3.1 乐观锁的典型场景

  • 读多写少,冲突相对较少;
  • 允许“失败重试”的业务;
  • 不希望加锁阻塞,提高并发度:
    • 用户资料更新;
    • 配置信息修改;
    • 大部分后台管理类表单更新;
    • 一些简单扣减场景(如非极端抢购)。

3.2 版本号实现方式(最常用)

数据表设计:

CREATETABLEproduct(idBIGINTPRIMARYKEY,nameVARCHAR(50),stockINT,versionINTNOTNULL);

业务流程:

  1. 先查出数据:
SELECTid,stock,versionFROMproductWHEREid=1;
  1. 更新时带上版本条件:
UPDATEproductSETstock=stock-1,version=version+1WHEREid=1ANDstock>0-- 防止扣成负数ANDversion=#{oldVersion};
  1. 判断执行结果:
  • 如果UPDATE返回影响行数 = 1:说明版本匹配,更新成功;
  • 如果返回 0:说明数据已经被别人修改过(version 不相等 / stock 不够),当前更新失败,需要:
    • 重查数据后重试,或
    • 直接报“库存不足”/“数据已更新,请刷新”等。

**本质:**把“加锁串行化”变成“无锁 + 成功就成功,失败就重试/提示”。

3.3 其他乐观锁方式

  1. 时间戳字段
    通过比较update_time是否等于原值来判断是否被修改。

  2. 字段值对比
    不单独用版本号,而是直接比较旧值:

UPDATEuserSETbalance=balance+100WHEREid=1ANDbalance=#{oldBalance};
  1. CAS 思想(在内存 / Redis 等)
    类似compareAndSet(oldValue, newValue),仅在值没变时才更新。

3.4 乐观锁的优点

  • 不依赖数据库锁,减少阻塞,提高吞吐;
  • 在冲突少的情况下,整体性能非常好;
  • 实现简单(在业务层加版本字段 + 条件更新)。

3.5 乐观锁的缺点

  • 遇到高并发 & 高冲突的热点数据:

    • 失败重试次数可能很多;
    • 业务复杂时重试逻辑难写。
  • 并不能完全避免“丢更新”,真正安全依赖 SQL 条件

    • 如果更新语句写错(没带版本条件),就失去了保护。

四、乐观锁 vs 悲观锁 对比总结

4.1 思想层面对比

对比维度悲观锁乐观锁
冲突假设相信“会冲突”相信“很少冲突”
控制方式先加锁,再操作不加锁,提交时校验版本
读写关系写时阻塞其他读/写(视锁类型而定)读写通常不互相阻塞
失败代价一般不会失败(除死锁或异常)可能更新失败,需要重试或提示
实现位置多在数据库层(行锁、表锁等)多在业务/应用层(version 字段、CAS 等)

4.2 使用场景对比

场景类型推荐方案
银行转账、资金扣减悲观锁 + 严格事务
秒杀、抢购库存通常结合多种手段(限流、队列),乐观锁只是其中一环
一般配置更新、个人信息修改乐观锁
复杂多步更新且高度冲突更倾向悲观锁
分布式系统跨服务修改多采用乐观锁 + 重试,或分布式锁

五、MySQL + Java 中的常见用法

5.1 MySQL 中的悲观锁

-- 案例:扣减库存(悲观锁版本)BEGIN;SELECTstockFROMproductWHEREid=1FORUPDATE;-- 检查 stock >= 1UPDATEproductSETstock=stock-1WHEREid=1;COMMIT;

注意:

  • 事务必须显式开启;
  • FOR UPDATE 会加行锁,避免并发修改冲突。

5.2 MySQL 中的乐观锁

-- 先查SELECTid,stock,versionFROMproductWHEREid=1;-- 再更新(乐观锁)UPDATEproductSETstock=stock-1,version=version+1WHEREid=1ANDstock>0ANDversion=#{oldVersion};

在业务代码中判断:

  • 影响行数 = 1 → 成功;
  • 影响行数 = 0 → 失败,可能是库存不足或被别人先一步更新。

5.3 Java / JPA 中的乐观锁示例

例如使用 JPA / Hibernate:

@EntitypublicclassProduct{@IdprivateLongid;privateIntegerstock;@VersionprivateIntegerversion;}
  • 添加@Version字段后,JPA 在更新时会自动加上版本条件:
    • WHERE id = ? AND version = ?
  • 如果更新失败,会抛出乐观锁异常,需要业务做重试或处理。

六、如何选择:用乐观锁还是悲观锁?

可以记一个简单判断维度:

  1. 冲突概率高不高?

    • 高:更倾向用悲观锁;
    • 低:乐观锁更合适。
  2. 能不能接受重试?

    • 能:乐观锁没问题;
    • 不能(比如写操作很重、事务很复杂):更适合悲观锁。
  3. 并发量大小?

    • 极高并发 + 单热点:
      • 纯悲观锁会把大家都锁死;
      • 纯乐观锁失败率很高,也会浪费资源;
      • 通常要结合限流、队列、拆分热点等手段。
  4. 一致性要求:

    • 资金、余额这类强一致场景:
      • 更多配合悲观锁 / 严格事务;
    • 一般业务数据:
      • 乐观锁足够,必要时给用户一个“请刷新页面”的提示。

七、小结

  1. 悲观锁:

    • 特点:先上锁再操作,牺牲并发换取简单强一致;
    • 实现:SELECT ... FOR UPDATE、行锁、表锁等。
  2. 乐观锁:

    • 特点:先操作再校验,冲突失败就重试或提示;
    • 实现:版本号字段、时间戳、CAS。
  3. 如何选:

    • 冲突高 + 严格一致性 + 难以重试 → 倾向悲观锁;
    • 冲突低 + 允许失败重试 + 追求高并发 → 倾向乐观锁。

真正的生产系统里,往往是两种都用

  • 某些关键点用悲观锁兜底;
  • 大部分业务采用乐观锁 + 幂等 + 重试机制,来换取性能和扩展性。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/21 7:39:18

2、探索 Linux 命令行:开启自由计算之旅

探索 Linux 命令行:开启自由计算之旅 1. 时代背景与 Linux 的崛起 在当今时代,计算机无处不在,从小小的手表到大型数据中心,它们通过网络紧密相连,带来了个人赋能和创意自由的新时代。然而,过去几十年里,一些大型企业开始对全球大部分计算机实施控制,决定用户能做什么…

作者头像 李华
网站建设 2026/3/15 14:30:29

深入理解现代摄像机聚焦与变焦系统:从光学原理到代码实现

前言 最近在做一个水下ROV的视觉系统,需要实现自动对焦和电动变焦功能。查了不少资料,发现网上讲这块的文章要么太理论化,要么代码不完整。干脆自己整理一篇,把光学原理和工程实现都讲清楚。 本文会从最基础的透镜成像讲起&#x…

作者头像 李华
网站建设 2026/3/14 19:43:30

WPF智能搜索革命:AutoSuggestBox如何重塑用户交互体验

WPF智能搜索革命:AutoSuggestBox如何重塑用户交互体验 【免费下载链接】wpfui WPF UI在您熟悉和喜爱的WPF框架中提供了流畅的体验。直观的设计、主题、导航和新的沉浸式控件。所有这些都是本地化且毫不费力的。 项目地址: https://gitcode.com/GitHub_Trending/wp…

作者头像 李华
网站建设 2026/3/19 19:53:07

8、复杂网络环境下的网络配置与管理

复杂网络环境下的网络配置与管理 1. 内部服务器的NAT配置 在某些情况下,外部可见地址不可用或成本过高,且在主要作为防火墙的机器上运行多个服务不是理想选择,此时需在网关进行NAT配置。以一个包含邮件服务器、Web服务器和文件服务器的网络为例,网络规格要求运行以明文(h…

作者头像 李华
网站建设 2026/3/20 17:23:52

13、网络队列、整形、冗余及日志监控统计全解析

网络队列、整形、冗余及日志监控统计全解析 1. CARP 接口配置与安全加固 在备份节点上,可使用 ifconfig 命令检查每个 CARP 接口是否配置正确。示例如下: $ ifconfig carp0 carp0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 1500lladdr 00:00:5e…

作者头像 李华
网站建设 2026/3/15 18:46:55

革命性架构突破:ERNIE-4.5多模态大模型重构视觉认知范式

革命性架构突破&#xff1a;ERNIE-4.5多模态大模型重构视觉认知范式 【免费下载链接】ERNIE-4.5-VL-28B-A3B-Base-Paddle 项目地址: https://ai.gitcode.com/hf_mirrors/baidu/ERNIE-4.5-VL-28B-A3B-Base-Paddle 在人工智能多模态融合领域&#xff0c;一项颠覆性的技术…

作者头像 李华