乐观锁和悲观锁,到底该怎么选?

为什么你的秒杀系统总超卖?转账偶尔对不上账?很可能------你选错了锁。

  • 悲观锁:适合冲突多、不能出错的场景(转账、抢票)
  • 乐观锁:适合冲突少、允许失败的场景(点赞、浏览量)
  • 没有最好,只有最合适

先说结论:两种完全不同的思路

悲观锁:先占着,你们等着

就像占座位,我先坐上去,你们想坐?等我起来再说。

sql 复制代码
START TRANSACTION;

-- 锁住这条记录
SELECT balance FROM accounts WHERE user_id = 123 FOR UPDATE;

-- 扣钱
UPDATE accounts SET balance = balance - 100 WHERE user_id = 123;

COMMIT; -- 释放锁

优点:不会出错

缺点:慢,大家排队等

sequenceDiagram participant A as 用户A participant DB as 数据库 participant B as 用户B A->>DB: 加锁并读数据 B->>DB: 想读数据 Note over B: 只能等着 A->>DB: 修改完,提交 DB->>B: 现在可以了

乐观锁:先干活,冲突了再说

就像写文档,大家都可以改,但提交的时候检查一下有没有人抢先改过。

sql 复制代码
-- 看一眼当前版本号
SELECT stock, version FROM products WHERE id = 456;
-- 结果:库存100,版本号5

-- 减库存,但要求版本号还是5
UPDATE products 
SET stock = 99, version = 6 
WHERE id = 456 AND version = 5;

-- 如果version变了,这条SQL不生效,说明有人抢先了

优点:快,不用等

缺点:冲突多了失败率高

sequenceDiagram participant A as 用户A participant DB as 数据库 participant B as 用户B A->>DB: 读数据(版本5) B->>DB: 读数据(版本5) A->>DB: 更新(要求版本5) Note over DB: 成功,版本改成6 B->>DB: 更新(要求版本5) Note over DB: 失败,版本已经是6了

四个真实场景

场景1:淘宝秒杀

10万人抢1000台iPhone,冲突超级大。

  • 纯悲观锁?排队排死
  • 纯乐观锁?99%失败,疯狂重试

真实做法:混合

  1. Redis先筛出1000人
  2. 这1000人用悲观锁扣库存
flowchart LR A[10万请求] --> B[Redis预扣减] B --> C[1000人通过] B --> D[99000人直接返回售罄] C --> E[悲观锁扣DB库存] E --> F[生成订单]

场景2:银行转账

你给朋友转500块,绝对不能错。

必须用悲观锁

sql 复制代码
START TRANSACTION;

-- 同时锁住两个账户(按ID顺序,防止死锁)
SELECT balance FROM accounts 
WHERE user_id IN (123, 456) 
ORDER BY user_id 
FOR UPDATE;

-- 扣钱、加钱、记账
UPDATE accounts SET balance = balance - 500 WHERE user_id = 123;
UPDATE accounts SET balance = balance + 500 WHERE user_id = 456;
INSERT INTO transactions (...) VALUES (...);

COMMIT;

关键点:

  • 一定要按顺序加锁
  • WHERE条件要走索引,不然会锁整张表

场景3:微博点赞

一条热门微博,每秒几千人点赞。

用乐观锁+Redis

python 复制代码
# Redis直接加1,毫秒级
redis.incr("post:999:likes")

# 后台慢慢同步到MySQL

10001个赞和10005个赞,用户根本看不出来,但"点了半天没反应"用户能感受到。

场景4:演唱会抢票

一个座位只能卖一次,不能超卖。

用悲观锁+预锁定

sql 复制代码
START TRANSACTION;

-- 锁住座位
SELECT status FROM seats 
WHERE concert_id = 100 AND seat_no = 'A-12' 
FOR UPDATE;

-- 锁定15分钟,等你付款
UPDATE seats 
SET status = '锁定', user_id = 你的ID, lock_time = NOW()
WHERE concert_id = 100 AND seat_no = 'A-12';

COMMIT;

定时任务释放超时的:

sql 复制代码
-- 15分钟没付款?释放座位
UPDATE seats 
SET status = '可售' 
WHERE status = '锁定' 
AND lock_time < 15分钟前;

四个经典的坑

坑1:死锁

两个人互相等对方,谁也动不了。

错误:

  • 张三转李四:先锁123,再锁456
  • 李四转张三:先锁456,再锁123
  • 结果:互相等,死锁

正确:

sql 复制代码
-- 不管谁转谁,都按ID从小到大锁
SELECT * FROM accounts 
WHERE user_id IN (123, 456) 
ORDER BY user_id 
FOR UPDATE;

坑2:ABA问题

库存100 → 99 → 100,你以为没变,其实变过。

解决办法:用版本号

sql 复制代码
UPDATE products 
SET stock = 99, version = version + 1
WHERE id = 1 AND version = 旧版本号;

坑3:锁错了,锁了整张表

sql 复制代码
-- name没索引,结果把整张表锁了
SELECT * FROM users WHERE name = '张三' FOR UPDATE;

必须保证WHERE条件走索引。

坑4:疯狂重试

失败了立马重试,CPU直接100%。

正确做法:等一会儿再重试

java 复制代码
int retry = 0;
while (retry < 5) {
    if (更新成功) return true;
    Thread.sleep(10 * (1 << retry)); // 10ms, 20ms, 40ms...
    retry++;
}

性能测试数据

100万数据,1000线程同时改余额:

方案 每秒处理 平均耗时 失败率
悲观锁 1200次 350ms 0%
乐观锁(不重试) 8500次 50ms 95%
乐观锁(重试3次) 3200次 180ms 12%
混合策略 4500次 120ms 3%

混合策略最均衡。


怎么选?三个问题

flowchart TD A[你的场景] --> B{冲突多吗?} B -->|很多| C[悲观锁] B -->|不多| D[乐观锁] C --> E{能等吗?} E -->|能| F[直接用悲观锁] E -->|不能| G[混合策略] D --> H{失败能重试吗?} H -->|能| I[乐观锁+重试] H -->|不能| J[改用悲观锁] style F fill:#FFD700 style I fill:#90EE90 style G fill:#87CEEB

问自己:

  1. 冲突多不多? 多就悲观,少就乐观
  2. 能接受失败吗? 不能就悲观,能就乐观
  3. 速度重要吗? 重要就乐观+Redis

总结

悲观锁适合:

  • 转账、支付
  • 抢票、抢购
  • 库存扣减
  • 不能出错的操作

乐观锁适合:

  • 点赞、浏览量
  • 统计数据
  • 读多写少
  • 对性能要求高

真实项目往往混着用:

  • 看商品详情 → 乐观锁(快)
  • 真下单 → 悲观锁(准)

别纠结哪个"更好",懂业务,选对场景就行。

相关推荐
qq_2975746712 小时前
SpringBoot项目长时间未访问,Tomcat临时文件夹被删除?解决方案来了
spring boot·后端·tomcat
一个有梦有戏的人12 小时前
Python3基础:函数基础,解锁模块化编程新技能
后端·python
熊文豪12 小时前
CANN ops-transformer算子库架构与设计理念
深度学习·架构·transformer·cann
深圳行云创新12 小时前
微服务架构引入 AI 后,怎么统一研发和运维的标准规范?
人工智能·微服务·架构
逍遥德12 小时前
Sring事务详解之02.如何使用编程式事务?
java·服务器·数据库·后端·sql·spring
Zfox_12 小时前
CANN PyPTO 编程范式深度解析:并行张量与 Tile 分块操作的架构原理、内存控制与流水线调度机制
线性代数·矩阵·架构
Coder_Boy_13 小时前
基于SpringAI的在线考试系统-整体架构优化设计方案
java·数据库·人工智能·spring boot·架构·ddd
晚霞的不甘15 小时前
CANN 支持多模态大模型:Qwen-VL 与 LLaVA 的端侧部署实战
人工智能·神经网络·架构·开源·音视频
qq_2975746721 小时前
【实战教程】SpringBoot 实现多文件批量下载并打包为 ZIP 压缩包
java·spring boot·后端