redis的incr不支持条件自增,因其仅对单key原子操作,无法实现“满足条件才执行”的分支逻辑;并发下get+incr组合会超限,需用lua脚本将判断与修改封装为原子操作。

Redis 的 INCR 为什么不能直接做带条件的自增
因为 INCR 是原子操作,但只对单个 key 生效,不支持“如果满足某条件才执行自增”这种分支逻辑。比如想实现“用户今日点赞数 ≤ 5 才允许 +1”,纯命令组合(GET + INCR)在并发下会出错:两个请求同时读到 5,都判断通过,结果变成 7。
常见错误现象:(integer) 6 被返回,但实际数据库里存了 7;或者用 WATCH + MULTI 实现乐观锁,但高并发时大量失败重试,吞吐骤降。
根本原因:Redis 命令是单指令原子,跨指令逻辑必须靠服务端一次性执行——这就是 Lua 脚本存在的意义。
用 EVAL 执行 Lua 脚本实现条件自增
Redis 内置 Lua 解释器,所有命令在脚本内执行时具有原子性,且能访问当前数据库状态。关键不是“写 Lua”,而是把判断和修改封装成一个不可拆分的动作。
实操建议:
- 脚本里优先用
redis.call("GET", KEYS[1])获取当前值,别用redis.pcall除非你明确要捕获异常 - 数值比较前务必用
tonumber()转换,否则字符串 "5" > "10" 成立(字典序) - 返回值统一用
return输出整数,客户端好解析;不要返回 table 或 nil - 避免在脚本里做耗时操作(如循环上万次、调用外部 API),会阻塞整个 Redis 实例
示例:限制单日点赞上限为 5
redis-cli --eval /dev/stdin user:123:likes:20240520 <h3> <code>EVALSHA</code> 和脚本缓存的坑</h3> <p>每次用 <code>EVAL</code> 发送完整脚本,网络开销大,且 Redis 需重复解析。应该先用 <code>SCRIPT LOAD</code> 缓存,再用 <code>EVALSHA</code> 调用哈希值。</p><div class="aritcle_card flexRow artxards"> <div class="artcardd flexRow"> <a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill3464" title="Redis Skill - 高性能缓存管理"><img src="https://img.php.cn/upload/skill/000/000/081/178960683454849.jpg" alt="Redis Skill - 高性能缓存管理" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a> <div class="aritcle_card_info flexColumn"> <a rel="nofollow" href="/xiazai/skill3464" title="Redis Skill - 高性能缓存管理" class="overflowclass">Redis Skill - 高性能缓存管理</a> <p class="overflowclass">Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。</p> </div> <a rel="nofollow" href="/xiazai/skill3464" title="Redis Skill - 高性能缓存管理" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span> </a> </div> </div> <p>容易踩的坑:</p>
- 脚本内容哪怕多一个空格,
SCRIPT LOAD返回的 SHA1 就不同,EVALSHA会报错(error) NOSCRIPT No matching script. Please use EVAL. - 集群模式下,
KEYS参数必须全部落在同一个 slot,否则EVALSHA报CROSSSLOT Keys in request don't hash to the same slot - Redis 重启后脚本缓存丢失,生产环境必须预加载或兜底用
EVAL
验证是否已加载:用 SCRIPT EXISTS <sha1></sha1> 查,别假设它一定在。
Lua 脚本里怎么安全处理不存在的 key
Redis 的 GET 对不存在的 key 返回 nil,Lua 里 nil 参与算术会报错 attempt to perform arithmetic on a nil value。
正确做法只有两种:
- 显式判断:
if current == nil then current = 0 end - 用默认值:
local current = tonumber(redis.call("GET", KEYS[1])) or 0
千万别写 tonumber(redis.call("GET", KEYS[1])) + 0 —— 这会在 key 不存在时直接崩溃。
另外,INCR 本身对不存在的 key 会初始化为 0 再 +1,但你没法在 INCR 前做条件判断,所以还是得先 GET。
os.time() 或遍历小数组,也要测压看延迟毛刺。线上脚本上线前,必须用真实数据量 + 并发量跑一遍。










