悲观锁(Pessimistic Lock)
为什么叫 "悲观"?
因为它 "悲观" 地认为并发操作一定会发生冲突,所以在操作数据之前,先加锁,确保其他事务无法修改这条数据,直到当前事务完成。
实现方式(数据库层面):
-
SELECT ... FOR UPDATE
(MySQL) -
SELECT ... WITH (UPDLOCK)
(SQL Server) -
其他数据库的排他锁机制
特点:
-
先锁再操作,防止并发修改
-
适用于高并发写操作(如抢购、库存扣减)
-
可能降低并发性能(锁会阻塞其他事务)
示例(ThinkPHP):
乐观锁(Optimistic Lock)
为什么叫 "乐观"?
因为它 "乐观" 地认为并发操作不会冲突,所以 不加锁,而是在更新时检查数据是否被修改过(通常用版本号或时间戳)。
实现方式:
-
版本号机制(
version
字段) -
时间戳机制(
update_time
字段) -
CAS(Compare-And-Swap)(如 Redis)
特点:
-
不加锁,先操作再检查冲突
-
适用于读多写少的场景
-
冲突时需要重试或回滚
示例(ThinkPHP):
对比总结
特性 |
悲观锁(Pessimistic Lock) |
乐观锁(Optimistic Lock) |
---|---|---|
加锁方式 |
先加锁再操作( |
不加锁,更新时检查冲突 |
适用场景 |
高并发写操作(如抢购) |
读多写少(如文章编辑) |
性能影响 |
可能降低并发性能(锁阻塞) |
无锁,冲突时才处理 |
实现方式 |
数据库锁机制 |
版本号、时间戳、CAS |
冲突处理 |
其他事务会被阻塞 |
需要重试或回滚 |
如何选择?
-
悲观锁:数据竞争激烈,必须保证数据一致性(如支付、库存扣减)。
-
乐观锁:冲突概率低,希望提高并发性能(如文章编辑、评论更新)。
redis分布式锁:
这段代码的作用是 使用 Redis 的 SETNX
(SET if Not eXists)实现分布式锁,防止并发重复提交(如重复提现)。具体解析如下:
代码解析
-
setLockNx($withdrawLockKey, 30)
-
调用一个自定义函数(可能是封装了 Redis 的
SETNX
命令)。 -
$withdrawLockKey
:锁的唯一标识(如用户ID + 业务类型,例如withdraw:user123
)。 -
30
:锁的自动过期时间(单位:秒),防止锁未释放导致死锁。
-
-
!setLockNx(...)
-
如果获取锁失败(返回
false
),说明锁已存在(即当前有其他请求正在处理相同业务)。 -
直接返回错误提示,阻止重复操作。
-
-
json_exit_Base64(401, ...)
-
返回 HTTP 401 状态码和 Base64 编码的 JSON 错误消息(可能是项目约定的通信格式)。
-
提示用户:"处理中,请勿重复点击"。
-
-
注释说明
-
//提现锁,处理完手动解锁
:提示开发者需要在业务逻辑完成后 手动释放锁(如调用delLock($withdrawLockKey)
),否则锁会在 30 秒后自动过期。
-
实现原理(Redis 分布式锁)
-
加锁
通过 Redis 的
SETNX
命令实现原子性锁:(注:高版本 Redis 支持
SET $withdrawLockKey 1 NX EX 30
一步完成) -
解锁
业务处理完成后需手动删除 key:
适用场景
-
提现/支付防重:防止用户多次点击导致重复扣款。
-
秒杀/库存扣减:避免超卖问题。
-
任何需要分布式环境下的互斥操作(如定时任务防并发执行)。
注意事项
-
锁的粒度
-
锁的 key 需要足够具体(例如包含用户ID),避免不同用户互相阻塞。
-
-
锁的过期时间
-
过期时间(如30秒)需大于业务处理时间,但不宜过长(否则阻塞其他请求)。
-
-
原子性问题
-
确保
SETNX + EXPIRE
是原子操作(如用 Redis 2.6.12+ 的SET
命令带NX
和EX
参数)。
-
-
手动解锁
-
如果业务逻辑异常退出,需在
finally
块中释放锁,或依赖自动过期。
-