当前位置:首页 > 问答 > 正文

Redis里怎么灵活整合多个字段当联合主键用,实际操作分享

需要明确一点,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可视化工具会自动将其识别为文件夹层级,便于管理,在Redis Cluster模式下,这种键也有助于数据均匀分布。
  • 缺点

    • 灵活性有局限:如果你想查询“用户12345的所有订单”,直接拼接法就不好办了,因为键中包含了精确的日期,你需要用 KEYS order:12345:* 命令,但 KEYS 命令在生产环境是严禁使用的,它会阻塞Redis服务,这时就需要用到下面的方法。

使用集合(Set)或有序集合(Sorted Set)维护索引

为了解决直接拼接法无法进行范围或模式查询的问题,我们引入“索引”的概念,这就像一本书的目录一样,我们用一个单独的数据结构来记录所有符合某种条件的键。

继续上面的例子,需求升级:要高效地获取用户12345在2023年10月份的所有订单。

  • 实际操作

    1. 存储订单数据本身:和方法一一样,使用 HSET order:12345:20231027 ... 等命令存储每一笔订单。
    2. 创建月份索引:我们为用户每个月的订单创建一个集合(Set)。
      # 将这笔订单的键名,添加到代表"用户12345在2023年10月订单"的集合中
      SADD index:order:user:12345:202310 "order:12345:20231027"
      # 如果还有10月28日的订单,也加进去
      SADD index:order:user:12345:202310 "order:12345:20231028"
  • 查询数据

    Redis里怎么灵活整合多个字段当联合主键用,实际操作分享

    • 要查用户12345在2023年10月的所有订单,只需要:
      SMEMBERS index:order:user:12345:202310

      这个命令会返回一个列表:1) "order:12345:20231027" 2) "order:12345:20231028"

    • 你的应用程序可以遍历这个列表,再用 HGETALL 命令逐个取出订单的详细信息,这个过程可以使用管道(Pipeline)优化,减少网络往返时间。
  • 更高级的用法——有序集合(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

这种方法的核心是“空间换时间”,通过占用额外的存储空间来维护索引,换取极高的查询性能。

序列化整个对象作为值

这种方法比较特殊,它不再是“多个字段当联合主键”,而是“多个字段生成一个主键,然后把整个复合对象存起来”,适用于对象结构复杂,且总是整体存取的情况。

  • 实际操作
    1. 在你的应用程序中(例如用Java、Python),将多个字段组合成一个对象。
    2. 将这个对象序列化(如转换成JSON字符串、MessagePack或Protobuf等二进制格式)。
    3. 用方法一生成一个键,order:12345:20231027
    4. 直接使用 SET 命令将这个序列化后的字符串存入Redis。
      SET order:12345:20231027 '{"userId": 12345, "date": "20231027", "amount": 99.99, "status": "paid"}'
  • 优点
    • 一次读写:读取和写入都是一次操作,网络开销小。
    • 适合复杂结构:值可以是任何格式,非常灵活。
  • 缺点
    • 无法部分更新:如果想只修改金额,也必须把整个JSON字符串读出来,在程序中修改,再整个写回去。
    • 无法使用Redis内置命令:不能像Hash那样直接使用 HINCRBY(对数字做加法)等方便的命令。

总结与实际选择建议

根据我在项目中的经验,选择哪种方法取决于具体的业务场景:

  1. 绝大多数情况方法一(直接拼接)配合方法二(索引) 是最强大和灵活的组合,你需要仔细思考你的查询模式(怎么查数据),然后设计主键和索引,除了按用户和日期查,是否需要按商品ID查?那就再为商品ID建一个索引集合 index:order:item:item_a,里面存放所有包含item_a的订单键名。
  2. 对象结构简单、需要频繁部分更新:优先使用 方法一,用Hash类型存储。
  3. 对象结构复杂、总是整体读写、很少更新:可以考虑 方法三,使用JSON序列化。
  4. 最重要的原则忘记数据库的表思维,用Redis的思维去设计,在设计之初就问自己:“我将来会怎样查询这些数据?” 然后根据查询需求来反推键和索引应该如何设计,这个提前的设计过程,比任何具体的技术技巧都重要。

无论用哪种方法,都要注意Redis键的过期时间(TTL)设置,避免无用数据常驻内存,这也是良好Redis实践的一部分。