必须先删除String类型key再创建Hash,因Redis禁止对String key执行HSET;需用TYPE判断类型后DEL+HSET,且客户端应预解析JSON传入字段对,避免服务端JSON解析依赖。

直接用 EVAL 执行 Lua 脚本批量转换是可行的,但必须避开 Redis 的原子性陷阱和 Lua 类型限制——String 值不能自动“变成”Hash,得显式拆解、重建、删除原 key。
为什么不能直接用 HSET 覆盖 String key?
Redis 不允许对已存在的 String 类型 key 执行 HSET,会报错:WRONGTYPE Operation against a key holding the wrong kind of value。所以必须先删再建,或用 DEL + HSET 组合。
- 脚本里不能依赖“key 不存在”,得用
EXISTS判断类型,再决定是否DEL - 若业务要求零中断,需确保读写不交叉:转换期间禁止对该 key 写 String,否则可能丢失数据
-
DEL是原子操作,但DEL+HSET整体不是原子的——中间有窗口期,客户端可能读到 key 不存在
如何安全地把 user:123(String JSON)转成 user:123(Hash)?
典型场景:老系统存的是 JSON 字符串,新逻辑想用 Hash 分字段访问。Lua 里没有原生 JSON 解析,必须靠客户端预处理或约定格式(如 field1=val1&field2=val2)。若坚持在服务端解析 JSON,得用 cjson(仅限 Redis 7.0+ 且编译时启用了 --enable-modules),否则会报 cjson not found。
- 推荐做法:客户端解析 JSON 后,把字段名/值对传进 Lua 脚本,由脚本执行
HSET - 示例调用:
EVAL "if redis.call('TYPE', KEYS[1]) == 'string' then redis.call('DEL', KEYS[1]); redis.call('HSET', KEYS[1], unpack(ARGV)); end" 1 user:123 field1 val1 field2 val2 - 注意
unpack(ARGV)要求ARGV长度为偶数,否则HSET报错ERR wrong number of arguments
redis.call('TYPE', key) 返回值怎么判断才准?
TYPE 返回的是字符串,不是数字或布尔,必须用 == 'string' 比较,不能写 == 1 或 ~= nil。常见误判:
-
redis.call('EXISTS', key) == 1只说明 key 存在,不说明类型,可能已是 Hash 或 List - 没加
if判断就直接DEL,会误删非 String 类型的 key - 脚本中多次调用
TYPE开销小,但若批量处理上千 key,建议先用SCAN+ 客户端过滤出目标 key,再分批传给 Lua,避免单次 EVAL 超时
真正麻烦的不是语法,而是类型切换时的读写一致性——要么停写,要么双写过渡,Lua 脚本本身解决不了分布式并发下的竞态,它只负责“删旧建新”这一步的原子性。


















