Redis里怎么灵活整合多个字段当联合主键用,实际操作分享
- 问答
- 2026-01-01 14:32:52
- 5
需要明确一点,Redis本身没有像传统关系型数据库(比如MySQL)那样严格的“表”和“主键”概念,更不用说“联合主键”了,在MySQL里,你可能会定义一个主键为 PRIMARY KEY (user_id, order_date),但在Redis里,我们是通过设计“键名”(Key Name)的格式来模拟实现这种需求的,核心思想就是:把多个字段拼接或组合成一个有意义的、唯一的字符串,作为Redis的键。
下面分享几种最常见和灵活的实际操作方法。
直接拼接法(最常用)
这是最直观的方法,我们要存储用户的订单信息,需要根据用户ID和订单创建日期来唯一确定一笔订单,我们可以这样设计键名:
order:{user_id}:{date}
-
实际操作:假设用户ID是
12345,订单日期是20231027,那么这条订单数据的键就是order:12345:20231027。 -
存储数据:我们可以使用
HSET命令来存储订单的详细信息(如金额、状态、商品列表等),因为Hash类型非常适合存储对象。HSET order:12345:20231027 amount 99.99 status "paid" items "item_a,item_b"
-
查询数据:
- 查询这一单的详细信息:
HGETALL order:12345:20231027 - 只查询金额:
HGET order:12345:20231027 amount
- 查询这一单的详细信息:
-
优点:

- 简单明了:键的结构一眼就能看懂,符合直觉。
- 查询高效:直接通过完整的键名获取数据,是Redis最快的操作。
- 天然分区:使用冒号分隔是一种约定俗成的做法,一些Redis可视化工具会自动将其识别为文件夹层级,便于管理,在Redis Cluster模式下,这种键也有助于数据均匀分布。
-
缺点:
- 灵活性有局限:如果你想查询“用户12345的所有订单”,直接拼接法就不好办了,因为键中包含了精确的日期,你需要用
KEYS order:12345:*命令,但KEYS命令在生产环境是严禁使用的,它会阻塞Redis服务,这时就需要用到下面的方法。
- 灵活性有局限:如果你想查询“用户12345的所有订单”,直接拼接法就不好办了,因为键中包含了精确的日期,你需要用
使用集合(Set)或有序集合(Sorted Set)维护索引
为了解决直接拼接法无法进行范围或模式查询的问题,我们引入“索引”的概念,这就像一本书的目录一样,我们用一个单独的数据结构来记录所有符合某种条件的键。
继续上面的例子,需求升级:要高效地获取用户12345在2023年10月份的所有订单。
-
实际操作:
- 存储订单数据本身:和方法一一样,使用
HSET order:12345:20231027 ...等命令存储每一笔订单。 - 创建月份索引:我们为用户每个月的订单创建一个集合(Set)。
# 将这笔订单的键名,添加到代表"用户12345在2023年10月订单"的集合中 SADD index:order:user:12345:202310 "order:12345:20231027" # 如果还有10月28日的订单,也加进去 SADD index:order:user:12345:202310 "order:12345:20231028"
- 存储订单数据本身:和方法一一样,使用
-
查询数据:

- 要查用户12345在2023年10月的所有订单,只需要:
SMEMBERS index:order:user:12345:202310
这个命令会返回一个列表:
1) "order:12345:20231027" 2) "order:12345:20231028"。 - 你的应用程序可以遍历这个列表,再用
HGETALL命令逐个取出订单的详细信息,这个过程可以使用管道(Pipeline)优化,减少网络往返时间。
- 要查用户12345在2023年10月的所有订单,只需要:
-
更高级的用法——有序集合(Sorted Set): 如果我们需要按订单金额排序,或者按创建时间排序(但键名里已经有时间了),有序集合更强大。
- 创建带分数的索引:假设我们用订单的创建时间戳(一个整数)作为分数(Score)。
ZADD index:order:user:12345:sorted 1698393600 "order:12345:20231027" 1698480000 "order:12345:20231028"
- 查询数据:
- 查询所有订单(按时间戳正序):
ZRANGE index:order:user:12345:sorted 0 -1 - 查询某个时间段的订单(例如时间戳1698393600到1698566400之间的):
ZRANGEBYSCORE index:order:user:12345:sorted 1698393600 1698566400
- 查询所有订单(按时间戳正序):
- 创建带分数的索引:假设我们用订单的创建时间戳(一个整数)作为分数(Score)。
这种方法的核心是“空间换时间”,通过占用额外的存储空间来维护索引,换取极高的查询性能。
序列化整个对象作为值
这种方法比较特殊,它不再是“多个字段当联合主键”,而是“多个字段生成一个主键,然后把整个复合对象存起来”,适用于对象结构复杂,且总是整体存取的情况。
- 实际操作:
- 在你的应用程序中(例如用Java、Python),将多个字段组合成一个对象。
- 将这个对象序列化(如转换成JSON字符串、MessagePack或Protobuf等二进制格式)。
- 用方法一生成一个键,
order:12345:20231027。 - 直接使用
SET命令将这个序列化后的字符串存入Redis。SET order:12345:20231027 '{"userId": 12345, "date": "20231027", "amount": 99.99, "status": "paid"}'
- 优点:
- 一次读写:读取和写入都是一次操作,网络开销小。
- 适合复杂结构:值可以是任何格式,非常灵活。
- 缺点:
- 无法部分更新:如果想只修改金额,也必须把整个JSON字符串读出来,在程序中修改,再整个写回去。
- 无法使用Redis内置命令:不能像Hash那样直接使用
HINCRBY(对数字做加法)等方便的命令。
总结与实际选择建议
根据我在项目中的经验,选择哪种方法取决于具体的业务场景:
- 绝大多数情况:方法一(直接拼接)配合方法二(索引) 是最强大和灵活的组合,你需要仔细思考你的查询模式(怎么查数据),然后设计主键和索引,除了按用户和日期查,是否需要按商品ID查?那就再为商品ID建一个索引集合
index:order:item:item_a,里面存放所有包含item_a的订单键名。 - 对象结构简单、需要频繁部分更新:优先使用 方法一,用Hash类型存储。
- 对象结构复杂、总是整体读写、很少更新:可以考虑 方法三,使用JSON序列化。
- 最重要的原则:忘记数据库的表思维,用Redis的思维去设计,在设计之初就问自己:“我将来会怎样查询这些数据?” 然后根据查询需求来反推键和索引应该如何设计,这个提前的设计过程,比任何具体的技术技巧都重要。
无论用哪种方法,都要注意Redis键的过期时间(TTL)设置,避免无用数据常驻内存,这也是良好Redis实践的一部分。
本文由邝冷亦于2026-01-01发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:https://www.haoid.cn/wenda/72504.html
