DEL命令必然导致误删,因其不校验锁持有者就直接删除key,高并发下易删掉其他客户端持有的锁;必须用Lua脚本原子校验value后删除,并配合SET NX PX原子加锁及唯一UUID value。

为什么 DEL 命令必然导致误删
直接执行 DEL lock_key 是最典型的误删根源。它不检查锁的持有者,只要 key 存在就删——而高并发下,这个 key 很可能已经被另一个客户端重新获取。比如线程 A 执行业务超时,锁过期后被线程 B 拿走,A 结束后仍无脑 DEL,就把 B 的锁干掉了。
这不是概率问题,是确定性风险:只要业务耗时 > 锁过期时间,且存在竞争,误删就一定会发生。
解锁必须用 Lua 脚本做原子校验
Redis 保证单个 Lua 脚本内所有操作原子执行,中间不会插入其他命令。这是唯一能防止“先 GET 再 DEL”竞态的手段。
解锁脚本核心逻辑只有一行判断:
if redis.call('GET', KEYS[1]) == ARGV[1] then
return redis.call('DEL', KEYS[1])
else
return 0
end
使用时注意:
-
KEYS[1]是锁 key(如lock:order:123) -
ARGV[1]是加锁时写入的唯一 value(必须是全局唯一标识,推荐 UUID,不能用线程 ID) - 脚本返回
1表示删除成功,0表示未删除(value 不匹配或 key 不存在)
加锁阶段也要带唯一 value 和原子过期
光靠 Lua 解锁不够,加锁本身也得防断层。不能用 SETNX + EXPIRE 两步,否则第一步成功、第二步失败会导致死锁。
必须用原子命令设置 value 和过期时间:
- Java/Go/Python 客户端统一用
SET lock_keyNX PX 30000 -
NX保证互斥,PX毫秒级精度更可控,是该次加锁的唯一凭证 - 不要用
EX秒级单位,避免因网络延迟或 GC 导致实际过期时间偏差过大
容易被忽略的三个细节
很多人写了 Lua 脚本却还是出问题,往往栽在这几个点上:
- 客户端生成的
value不够唯一:多个实例用相同机器名+进程 ID,或没加随机因子,导致不同客户端拿到重复 value - Lua 脚本没做
redis.call('GET', KEYS[1])非空判断,当 key 已被删时脚本报错退出,返回nil而非0,上层误判为成功 - 锁 key 的命名没包含业务上下文(如用户 ID、订单号),导致不同资源共用一个 key,锁粒度失控
真正安全的分布式锁,不是“有没有用 Lua”,而是从加锁 value 的生成、key 的作用域、到解锁脚本的健壮性,每个环节都经得起并发推演。











