redis 的 hincrby 不支持多字段原子递增,因无原生批量命令且 multi/exec 无法保证 hash 多字段更新的原子性;lua 脚本是唯一稳妥方案,通过 cjson.decode 解析 json 参数并循环调用 hincrby 实现原子操作。

为什么不能直接用 HINCRBY 对多个字段批量原子递增
Redis 的 HINCRBY 只支持单个 field,没有原生的批量版本。如果用多次 HINCRBY 操作多个字段,中间可能被其他客户端干扰——比如 A 客户端刚加完 field1,B 客户端就修改了 field2,导致最终状态不一致。这不是“事务失败”,而是根本没事务边界,连 MULTI/EXEC 都救不了:Hash 的多个字段更新无法被包装进一个原子执行单元。
用 Lua 脚本封装多字段 HINCRBY 是最稳妥的方案
Lua 脚本在 Redis 中以原子方式执行,整个脚本运行期间不会被其他命令打断。关键是要把 hash key、field 列表、对应增量都作为参数传入,避免硬编码。
- 脚本接收 3 个参数:
KEYS[1](hash key),ARGV[1](JSON 字符串,形如"{\"a\":1,\"b\":-2,\"c\":10}"),可直接用cjson.decode - 必须检查
ARGV[1]是否为合法 JSON,否则redis.call("hincrby")会因非数字值报错ERR hash value is not an integer - Redis 7.0+ 支持
cjson内置,6.x 需确认是否启用(默认开启);若禁用,得改用字符串解析(不推荐)
local data = cjson.decode(ARGV[1])
for field, incr in pairs(data) do
redis.call("hincrby", KEYS[1], field, incr)
end
return 1
实际调用时注意参数格式和错误处理
客户端传参稍有不慎就会触发 Lua 报错,比如 JSON 多余逗号、整数溢出、field 名含空格等。错误信息通常是 ERR Error running script 后跟 Lua traceback,但真实原因藏在末尾——例如 attempt to perform arithmetic on a nil value 往往是字段名拼错或 JSON 解析失败。
- Python 示例(redis-py):
r.eval(lua_script, 1, "myhash", '{"score":5,"level":1}') - 字段名和增量必须为合法 JSON 键值对;value 必须是整数(
1、-3),不能是字符串"5"或浮点数5.0 - 如果某个 field 原来不存在,
HINCRBY会自动初始化为 0 再加,行为符合预期 - 不要在脚本里做耗时操作(如循环上万次),否则阻塞 Redis 主线程
替代方案对比:Pipeline 不等于原子性
有人尝试用 pipeline 批量发多个 HINCRBY,看起来“一起发”,但本质仍是多个独立命令,只是减少了网络往返。服务端仍逐条执行,中间完全可能被插入其他客户端命令。它提升的是吞吐,不是一致性。
-
pipeline.execute()返回结果数组,但无法保证这组操作的中间态对外不可见 - 真正需要“全部成功或全部不生效”语义时,只有 Lua(或 Redis 事务 + watch,但 watch 对 hash 字段粒度太粗,不实用)
- 如果业务允许最终一致,且字段间无强依赖,pipeline 更轻量;否则别妥协
HGETALL 校验。










