乐观锁、悲观锁和分布式锁的使用
后台开发最怕什么?数据被改乱。 把数据库想成公共厨房,锅就是数据。 三个人同时炒菜,锅翻了,菜就糊了。 锁就是排队机制,让锅一次只给一个人用。
下面讲讲我们3种最常用的锁:乐观锁、悲观锁、分布式锁。
1. 乐观锁
使用场景
适合冲突少的场景。比如用户修改自己的资料、库存更新不频繁的情况。
实际案例
我们有个商品表,结构大概是这样的:
sql
CREATE TABLE product (
id INT PRIMARY KEY,
name VARCHAR(50),
stock INT,
version INT DEFAULT 0
);
注意那个version
字段,这就是乐观锁的关键。
当用户下单时,我们会这样更新库存:
sql
UPDATE product
SET stock = stock - 1, version = version + 1
WHERE id = 1001 AND version = 2;
只有当当前版本是2的时候,才允许扣减库存,并把版本号加1。
如果两个请求同时来,第一个执行完后版本变成3,第二个请求因为version=2
不成立,更新就会失败。这时候程序就知道:"哦,有人抢先改了,我得重新查一下库存再处理。"
小结
- 优点:性能好,不阻塞
- 缺点:冲突多了会反复重试
- 适合:读多写少、并发不高的场景
2. 悲观锁:先上锁,再操作
使用场景
适合冲突频繁的场景。比如秒杀、抢票这种大家一窝蜂涌进来的情况。
实际案例
还是上面那个商品表,但这次我们用悲观锁:
sql
BEGIN;
-- 先锁定这条记录,别人不能动
SELECT * FROM product WHERE id = 1001 FOR UPDATE;
-- 查到库存是5,减1
UPDATE product SET stock = 4 WHERE id = 1001;
COMMIT;
FOR UPDATE
这个语句很关键,它会把这行数据锁住,直到事务结束。其他人想执行同样的查询,就得排队等着。
这样就能保证同一时间只有一个请求能修改库存,彻底避免超卖。
小结
- 优点:简单直接,不会超卖
- 缺点:性能差,容易阻塞
- 适合:高并发、强一致性的场景
3. 分布式锁:跨机器的协调
使用场景
当你有多个服务实例(比如部署了3台服务器),单机锁不管用了,就得用分布式锁。
实际案例
我们系统做了集群,3台服务器一起处理订单。这时候用数据库锁或synchronized都没用------锁不住别的机器上的进程。
我们用了 Redis 来实现分布式锁:
java
// 伪代码
String lockKey = "lock:product_1001";
// 尝试加锁,3秒自动过期
Boolean locked = redis.set(lockKey, "1", "NX", "EX", 3);
if (locked) {
try {
// 执行扣库存逻辑
deductStock(1001);
} finally {
// 释放锁
redis.del(lockKey);
}
} else {
// 加锁失败,说明别人正在处理
System.out.println("商品太火爆,再试一次!");
}
这里用到了 Redis 的 SET key value NX EX
命令:
NX
:只有key不存在时才设置EX
:3秒后自动过期(防止程序崩溃导致锁永远不释放)
小结
- 优点:能跨服务协调
- 缺点:依赖Redis,实现要小心死锁
- 适合:分布式系统、集群环境
什么时候用哪种锁?
我总结了个简单判断方法:
场景 | 推荐用锁 |
---|---|
单机应用,偶尔更新 | 乐观锁 |
单机应用,高频修改 | 悲观锁 |
多台服务器部署 | 分布式锁 |
读多写少 | 乐观锁 |
写操作特别频繁 | 悲观锁 or 分布式锁 |
最后一点心得
锁不是越多越好,而是够用就好。 先想清楚"锅会不会翻",再决定"怎么排队"。 技术就是解决问题的工具!用顺手了,也就没那么可怕了。
本文首发于微信公众号「刘大华的开发笔记」我是大华,专注分享前后端开发的实战笔记。 关注我,少走弯路,一起进步!