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

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

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

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

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

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

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

总结

悲观锁适合:

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

乐观锁适合:

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

真实项目往往混着用:

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

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

相关推荐
脚本自动化开发2 小时前
玩影之刃零,两张四千元级显卡怎么挑
后端
怕浪猫2 小时前
这个十几个agent 协作项目值得关注
人工智能·面试·github
码客-ygluu3 小时前
X3 LUA 异步分布式微服务集群框架
c++·架构·lua
用户47949283569155 小时前
从一句话或代码仓到可交互 HTML:开源 Agent 画图技能 Archify v3.2 实测与选型边界
前端·后端·面试
努力的小雨6 小时前
别再复制日志给AI了!我让Codex自己去观测云查报错
后端
Decay__8 小时前
VMware 安装 SQL Server 双节点 AlwaysOn 可用性组教程 —— WSFC、AG 与侦听器完整部署(系列核心篇)
运维·数据库·sql·mysql·架构
wangruofeng8 小时前
从 GIF 到 AVIF:网页图片格式演进与 2026 年选型指南
前端·架构
afumu8 小时前
Go 写一个只监听 127.0.0.1 的控制服务:鉴权、白名单、危险命令拦截
后端·安全·go
johnjincaaaa9 小时前
猿人学第 18 题「jsvmp 洞察先机」逆向通关实录
javascript·后端
Softmaxmap9 小时前
从零做一个中文小模型:600 亿 token 数据、mHC + Engram 结构、到强化学习的全流程(开源)
架构