不能直接用 for 遍历千万级 HASH 或 SET 的 Lua 脚本,因为 Redis 会阻塞、超时甚至 OOM;HGETALL 等 *ALL 命令一次性加载全量数据至 Lua 表,内存翻倍且不可中断,500 万字段即可能失败;应改用 HSCAN 等游标命令分批处理,由客户端驱动循环,每次只处理 COUNT 量级数据,并注意游标类型、COUNT 设置及限流。

不能直接用 for 遍历千万级 HASH 或 SET 的 Lua 脚本 —— Redis 会阻塞,超时,甚至触发 OOM。
为什么 redis.call("HGETALL", key) 在 BigKey 场景下必然失败
Redis Lua 沙箱对单次脚本执行有严格内存与时间限制:HGETALL 会一次性把整个 HASH 加载进 Lua 表,内存翻倍(原始数据 + Lua table 开销),且遍历过程无法中断。实测 500 万字段的 HASH,HGETALL 返回后 Lua 表构建就可能卡死或报 OOM command not allowed when used memory > 'maxmemory'。
- 所有
*ALL类命令(HGETALL、SMEMBERS、LRANGE key 0 -1)都适用此规则 - Redis 7.0+ 的
FCALL_RO也不缓解该问题,因为数据仍需全量加载到 Lua 栈 - 即使脚本逻辑只取前 100 项,
HGETALL仍会拉取全部
正确做法:用游标分批读取 + 客户端协同
必须放弃“纯 Lua 一次性处理”的思路,改用服务端游标命令(HSCAN、SSCAN、ZSCAN)配合客户端循环。Lua 脚本只做单次游标处理,返回结果和新游标,由调用方决定是否继续。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 每次 Lua 调用只执行一次
HSCAN key cursor COUNT 1000,避免内存堆积 - 脚本返回
{next_cursor, processed_count, sample_result}结构,例如:return {cursor, #keys, keys} - 客户端(如 Python/Go)用 while 循环驱动,直到
cursor == "0" - 注意:Lua 中
cursor是字符串(如"123"),不可当数字加减;COUNT建议设为 500–2000,过大仍可能超时
常见踩坑点:游标误用、类型混淆、未限流
很多团队在迁移时栽在细节上,不是逻辑错,而是边界没控住。
-
HSCAN返回的cursor必须原样传回下次调用,不能tonumber()再转回字符串 —— Redis 6.0+ 游标可能是大整数或 base64 字符串 - 对
SCAN类命令未设置COUNT,默认值是 10,导致千万级 Key 要发百万次请求 - 在 Lua 中对扫描结果做复杂计算(如拼接 JSON、排序),反而把压力从网络转移到 Lua 执行引擎
- 没在客户端加
time.Sleep(1ms)级别微延迟,高频 SCAN 可能打满 Redis CPU,影响线上请求
替代方案:优先考虑 UNLINK + 异步清理,而非遍历
如果目标只是清理、统计或迁移 BigKey,绝大多数情况根本不需要“遍历” —— 直接删、导出、或用 Redis 自带的异步机制更稳。
- 删 BigKey?用
UNLINK key(非DEL),它把释放内存操作丢给后台线程 - 要导出数据?用
redis-cli --scan --pattern "key:prefix:*" | xargs -L 5000 redis-cli MGET,绕过 Lua 限制 - 需要实时统计?改用 HyperLogLog(去重计数)或 PFADD + PFCOUNT,而不是存千万级 SET 后再
SCARD - 真要遍历分析?先用
MEMORY USAGE key和DEBUG OBJECT key确认编码类型(hashtablevsziplist),再决定 SCAN 策略
真正难的不是写对一行 HSCAN,而是判断“此刻是否真的需要遍历”。千万级 BigKey 本身已是反模式,脚本只是止痛药,根治得靠数据建模重构。

















