数据库的乐观锁和悲观锁的区别

1. 悲观锁 (Pessimistic Lock) ------ "占着茅坑不拉屎"

核心心态: 悲观。我觉得总有人 想害朕(想抢我的数据)。
做法: 只要我开始用这条数据,我就把门锁死。在我处理完之前,谁也别想碰,连"读"都不行,都在外面排队!

生活比喻:上公厕
  1. 你进了一个单间。
  2. 咔嚓,把门插销锁上(数据库行锁)。
  3. 你在里面玩手机、看报纸(处理业务逻辑)。
  4. 外面来了 100 个人,推门发现打不开,只能在门口干等着(阻塞 Blocked)。
  5. 你完事了,开门出来(提交事务 Commit)。
  6. 下一个人才能进去。
SQL 是怎么写的?

你需要显式地告诉数据库"给我锁上":

sql 复制代码
-- 开启事务
BEGIN;
-- 重点是 FOR UPDATE,这句话一出,这行数据就被你独占了
SELECT stock FROM goods WHERE id = 1 FOR UPDATE; 

-- 在这期间,别的线程执行 SELECT ... FOR UPDATE 会被卡死在这里
UPDATE goods SET stock = stock - 1 WHERE id = 1;

COMMIT; -- 只有等你提交了,锁才释放
  • 优点: 绝对安全,数据绝对一致。
  • 缺点: 性能极差。万一你在里面死锁了或者处理太慢,整个系统就瘫痪了。
  • 适用场景: 数据极其敏感,且并发量很低(比如银行转账)。

2. 乐观锁 (Optimistic Lock) ------ "Git 代码冲突"

核心心态: 乐观。我觉得大部分时间 没人跟我抢。
做法: 我不锁门,大家随便进,随便读。但是我提交修改的时候,我要检查一下,在我修改期间,有没有别人偷着改过数据。

生活比喻:Git 提交代码
  1. Pull: 你和小王都把 index.vue 拉到了本地(Version 1)。
  2. Edit: 你在本地改代码,小王也在本地改代码。互不影响。
  3. Push(关键时刻):
    • 小王手快,先提交了(Version 变成 2)。
    • 你后提交,Git 告诉你:"冲突了!你的版本(V1)过期了,现在的最新版是 V2。"
  4. Retry: 你只能重新拉取最新代码,合并一下,再提交。
数据库怎么实现?(重点:版本号机制)

通常我们在表里加一个字段:version (版本号)。

  1. 第一步:查数据

    • 线程 A 查到:stock = 1, version = 1
    • 线程 B 查到:stock = 1, version = 1
  2. 第二步:线程 A 抢先修改

    sql 复制代码
    -- 我要把 version 变成 2,前提是现在的 version 必须还是 1
    UPDATE goods SET stock = 0, version = 2 
    WHERE id = 1 AND version = 1;
    • 结果: 成功!数据库里的 version 变成了 2。
  3. 第三步:线程 B 慢了一拍去修改

    sql 复制代码
    -- B 手里拿的 version 还是 1,它想把 version 变成 2
    UPDATE goods SET stock = 0, version = 2 
    WHERE id = 1 AND version = 1; -- 👈 此时数据库里的 version 已经是 2 了!
    • 结果: 查找条件 version = 1 匹配不到任何行。更新失败(影响行数 0)。
    • 应用层反应: 给用户报错"手慢了",或者自动重试。
  • 优点: 吞吐量极高,不用排队,大家都在跑。
  • 缺点: 如果冲突太频繁(比如 100 个人抢 1 个),会有 99 个人失败,需要应用层处理重试逻辑。
  • 适用场景: 互联网大部分场景(秒杀、点赞、评论)。

3. 在 Java (MyBatis-Plus) 里怎么用?

记得我之前推荐你的 MyBatis-Plus 吗?它把乐观锁封装成了"傻瓜式"操作。

第一步: 数据库表里加个字段 version,默认值 1。

第二步: Java 实体类加个注解。

java 复制代码
public class Goods {
    private Long id;
    private Integer stock;
    
    @Version // 👈 就加这一个注解,告诉 MP 这是乐观锁字段
    private Integer version;
}

第三步: 配置一下插件(现在的代码里只需要注册一个 Bean)。

第四步: 正常写代码。

java 复制代码
// 1. 先查出来 (必须先查,拿到当前的 version)
Goods goods = goodsMapper.selectById(1); // 假设此时 version=1

// 2. 修改数据
goods.setStock(goods.getStock() - 1);

// 3. 更新
// MyBatis-Plus 会自动把你生成的 SQL 变成:
// UPDATE goods SET stock=0, version=2 WHERE id=1 AND version=1
int result = goodsMapper.updateById(goods);

if (result == 0) {
    return "抢购失败,被人截胡了!"; // 没抢到锁
}

总结

  • 悲观锁: FOR UPDATE。先锁后干。像红绿灯,红灯停绿灯行。
  • 乐观锁: version 字段。先干后查。像斑马线,看着没人才走,走到一半发现有车来了就退回来。

在面试里,只要你能说出**"为了性能,我们一般优先使用基于版本号的乐观锁,除非是像金额扣减这种极度敏感的业务才考虑悲观锁"**

相关推荐
东方佑16 分钟前
MA-RMSNorm:打破“缩放换智能”的魔咒,大模型归一化的一次范式革命!
数据库·人工智能·计算机视觉
云和恩墨44 分钟前
「云帆计划·长沙站」活动成功举办,云和恩墨与紫薇垣和湖南金悦科技分别签约并授牌,本地伙伴生态建设加速
数据库·科技
huahailing10241 小时前
Spring Boot 集成 XXL-Job 完整实现方案(支持动态CRUD)
java·spring boot·后端
薛定谔的猫-菜鸟程序员1 小时前
基于 Electron 的本地短视频解析与下载工具:架构设计与工程实践
java·electron·音视频
千维百策6662 小时前
如何管理错误预算:提升服务可靠性与 SLO 达成率的实践经验
服务器·网络·数据库
音符犹如代码2 小时前
Arthas Profiler 火焰图实战:CPU 热点在哪一目了然
java·jvm·spring boot
小园子的小菜2 小时前
Java 并发编程:线程安全队列全解 —— 阻塞与非阻塞实现原理及源码深度剖析
java·开发语言
好好沉淀2 小时前
Windows 下升级 Maven 3.6.1 到 3.9.9 踩坑全记录
java·windows·maven
CodexDave3 小时前
Python 自动化接单实战(一):把 CSV 需求做成配置驱动解析器
java·python·自动化·json·数据清洗·python自动化·csv处理
Y3815326623 小时前
2026 年 7 月,SERP API 调用的稳定性实战:超时、重试、降级
开发语言·数据库·人工智能·php