hweiyu00头像
Redis命令:SUNIONSTORE封面图

Redis命令:SUNIONSTORE

Redis SUNIONSTORE 命令详细教程

SUNIONSTORE 计算多个集合的并集,并把结果写入指定的目标 Key,返回写入的成员数量。它从 Redis 1.0.0 起提供,复杂度为 O(N)(N 为所有参与运算集合的元素总数)。它是 SUNION 的写版本:SUNION 把并集返回给客户端,SUNIONSTORE 把并集留在 Redis 里,便于缓存、二次运算或分批读取。目标 Key 会被完全覆盖,结果为空时目标 Key 会被删除。

资料合集:https://pan.quark.cn/s/10e98d308913、https://pan.quark.cn/s/f56bc69c5338

一、概览与语法

SUNIONSTORE destination key [key ...]
项目说明
数据类型Set
支持版本Redis 1.0.0 起
destination结果写入的目标 Key,已存在时被覆盖
key一个或多个参与运算的集合,全部参与求并集
返回值整数,写入目标集合的成员数量;结果为空时返回 0
时间复杂度O(N),N 为所有给定集合的元素总数
ACL@write、@set、@slow
命令标记write、denyoom

官方说明:计算所有指定集合的并集并存储到 destination 中。若 destination 已存在则被覆盖。$TRAE_REF

二、基础示例

以下命令在测试实例的 redis-cli 中执行。文中结果是预期说明,未实际连接 Redis 运行。

DEL tutorial:{sunionstore}:key1 tutorial:{sunionstore}:key2 tutorial:{sunionstore}:key
SADD tutorial:{sunionstore}:key1 "a" "b" "c"
SADD tutorial:{sunionstore}:key2 "c" "d" "e"
SUNIONSTORE tutorial:{sunionstore}:key tutorial:{sunionstore}:key1 tutorial:{sunionstore}:key2
SCARD tutorial:{sunionstore}:key
SMEMBERS tutorial:{sunionstore}:key

预期结果:两条 SADD 各返回 3;SUNIONSTORE 返回 5;SCARD 返回 5;SMEMBERS 返回 a、b、c、d、e 共 5 个成员(顺序不保证)。重复成员 c 只出现一次。

三、destination 的覆盖与 TTL 重置

这是本命令最容易出问题的行为:

destination 原状态运算后
不存在创建并写入结果
已存在且结果非空旧内容被整体覆盖,旧成员全部丢失
已存在且结果为空destination 被删除,Key 不再存在
是其他数据类型被覆盖为 Set,不报错
有 TTLTTL 被清除,新 Key 永久存在
DEL tutorial:{sunionstore}:overwrite tutorial:{sunionstore}:s1 tutorial:{sunionstore}:s2
SADD tutorial:{sunionstore}:overwrite "old1" "old2"
EXPIRE tutorial:{sunionstore}:overwrite 1000
SADD tutorial:{sunionstore}:s1 "x" "y"
SADD tutorial:{sunionstore}:s2 "y" "z"
SUNIONSTORE tutorial:{sunionstore}:overwrite tutorial:{sunionstore}:s1 tutorial:{sunionstore}:s2
SMEMBERS tutorial:{sunionstore}:overwrite
TTL tutorial:{sunionstore}:overwrite

预期结果:SUNIONSTORE 返回 3;SMEMBERS 返回 x、y、z,旧的 old1、old2 已被清除;TTL 返回 -1,原来设置的 1000 秒过期时间已丢失。

这两点经常成对踩坑:覆盖是 replace 而不是 merge(旧数据全丢),覆盖后 TTL 也一起丢(缓存的 Key 从此永不过期,悄悄变成常驻内存)。把 SUNIONSTORE 用作"预计算缓存"时,必须在写入后立刻重新 EXPIRE。

四、destination 参与运算

destination 可以与源 Key 同名,Redis 会先完整计算再写入,不会出现边算边改的问题:

DEL tutorial:{sunionstore}:self
SADD tutorial:{sunionstore}:self "a" "b"
SUNIONSTORE tutorial:{sunionstore}:self tutorial:{sunionstore}:self
SMEMBERS tutorial:{sunionstore}:self

预期结果:SUNIONSTORE 返回 2;SMEMBERS 返回 a、b。只传一个 Key 时并集就是它自己,因此这是一种"原地去重/重建"的写法。

常见用法是增量累加:

DEL tutorial:{sunionstore}:acc tutorial:{sunionstore}:daily
SUNIONSTORE tutorial:{sunionstore}:acc tutorial:{sunionstore}:acc tutorial:{sunionstore}:daily

预期结果:读起来是"把今天的集合并入累计集合",但注意 destination 同时作为源出现时,可读性差且容易让维护者误以为写错了。更清晰的写法是把结果写到独立 Key(如 acc:result),再用 RENAME 替换,或者直接用 SADD acc member... 累加。

五、空结果的删除语义

并集为空只发生在所有源 Key 都不存在(或都是空集合)时:

DEL tutorial:{sunionstore}:empty
SUNIONSTORE tutorial:{sunionstore}:empty tutorial:{sunionstore}:nokey1 tutorial:{sunionstore}:nokey2
EXISTS tutorial:{sunionstore}:empty
SCARD tutorial:{sunionstore}:empty

预期结果:SUNIONSTORE 返回 0;EXISTS 返回 0;SCARD 返回 0。三者一致说明 destination 已被删除,而不是留下一个空集合。

对缓存场景的启示:结果集合消失后,读取方看到的是"Key 不存在",而不是"空集合"。用 SCARD 判断时都会得到 0,但用 EXISTS 或 TYPE(返回 none)可以确认它确实不存在,需要在监控里区分"未生成"和"已生成但为空"这两种状态。

六、边界情况与错误处理

场景行为
只传 destination 不传 key报参数数量错误(至少需要 3 个参数)
只传一个 key结果等于该集合本身,返回其成员数量
任一源 Key 不存在视为空集合;全部不存在则结果为空
任一源 Key 类型不是 Set报 WRONGTYPE,destination 不被修改
destination 是其他类型被覆盖为 Set,不报错
结果为空destination 被删除,返回 0
写满内存报 OOM 错误(命令带 denyoom 标记)
SET tutorial:{sunionstore}:str text
SADD tutorial:{sunionstore}:src "one"
SUNIONSTORE tutorial:{sunionstore}:dst tutorial:{sunionstore}:src tutorial:{sunionstore}:str
EXISTS tutorial:{sunionstore}:dst
SUNIONSTORE tutorial:{sunionstore}:dst2

预期结果:第一条因源 Key str 是 String 类型报 WRONGTYPE;EXISTS 返回 0,证明失败时 destination 没有被创建或修改(失败是原子的);最后一条缺少参与运算的 key,报 ERR wrong number of arguments for 'sunionstore' command。

"destination 是其他类型也能被覆盖"这一点与源 Key 的严格校验形成对比:源 Key 类型不对会整体失败,destination 类型不对则直接覆盖。这意味着误把业务数据 Key 当作 destination 时会静默毁掉原数据,而且不会报错。

七、与相近命令的区别

命令结果去向是否覆盖目标是否传输成员返回
SUNIONSTORE写入 destination是(含 TTL 丢失)否写入数量
SUNION只返回客户端无是成员数组
SINTERSTORE交集写入 destination是否写入数量
SDIFFSTORE差集写入 destination是否写入数量
SMEMBERS读取单个集合无是成员数组
SADD向集合追加成员否(累加)否新增数量

选择要点:

  • 只想本次用一下并集:SUNION,不要多写一次磁盘/内存。
  • 结果会被多次读取、或结果很大不想反复传输:SUNIONSTORE + EXPIRE。
  • 想累加到已有集合:SADD(SADD dest member...),或用 SUNIONSTORE 的"自引用"写法,但后者会重建整个集合(O(N))。
  • 只关心数量:SUNION 拿不到数量之外的省事方案,但可以用 SUNIONSTORE 后 SCARD(有写入成本),或在客户端对 SUNION 结果取长度。

八、客户端示例

前提为已安装 redis-py 并准备好本地测试实例。redis-py 的 sunionstore(dest, keys) 返回写入数量。

import redis

r = redis.Redis(host="localhost", port=6379, decode_responses=True)
k1 = "tutorial:{sunionstore}:py1"
k2 = "tutorial:{sunionstore}:py2"
dest = "tutorial:{sunionstore}:pydest"
try:
    r.delete(k1, k2, dest)
    r.sadd(k1, "a", "b", "c")
    r.sadd(k2, "c", "d")

    print(r.sunionstore(dest, [k1, k2]))   # 4
    print(sorted(r.smembers(dest)))        # ['a', 'b', 'c', 'd']

    # 覆盖 + TTL 重置:写入后必须重新设置过期时间
    r.expire(dest, 60)
    print(r.ttl(dest))                     # 约 60

    r.delete(k1, k2)                       # 源集合都消失
    print(r.sunionstore(dest, [k1, k2]))   # 0,结果为空
    print(r.exists(dest))                  # 0,destination 被删除
finally:
    r.delete(k1, k2, dest)
    r.close()

Java(Jedis)示例,sunionstore(dstkey, keys...) 返回 long:

try (Jedis jedis = new Jedis("localhost", 6379)) {
    String k1 = "tutorial:{sunionstore}:java1";
    String k2 = "tutorial:{sunionstore}:java2";
    String dest = "tutorial:{sunionstore}:javadest";
    jedis.del(k1, k2, dest);
    jedis.sadd(k1, "a", "b", "c");
    jedis.sadd(k2, "c", "d");

    System.out.println(jedis.sunionstore(dest, k1, k2));  // 4
    System.out.println(jedis.smembers(dest));             // [a, b, c, d]
    jedis.expire(dest, 60);                               // 覆盖后必须重设 TTL
    jedis.del(k1, k2, dest);
}

九、原子性与并发

单命令原子:计算与写入在同一条命令内完成,其他客户端看不到"算完但未写"的中间状态。要么看到旧内容,要么看到完整的新内容。

并发覆盖:多个生产者向同一 destination 执行 SUNIONSTORE 时,最终内容取决于执行顺序,后执行者完全覆盖先执行者。不要把同一个 destination 当作多方共享的累加器——那样会丢数据,应改用 SADD 累加。

结果不是快照锁定:命令结束后 destination 是普通集合,可以被后续 SADD/SREM 修改,也会因 TTL 到期而消失。

事务与 Lua:可在 MULTI/EXEC、Lua、Pipeline 中使用。需要在"生成结果 + 设置 TTL + 通知下游"之间保持原子性时,用 Lua 把这几步包在一起最稳。

集群限制:destination 与所有源 Key 必须落在同一哈希槽,否则报 CROSSSLOT。给所有 Key 加相同哈希标签即可(例如 {cart}),但要注意这会把这些 Key 绑到同一节点。

十、性能与使用注意事项

O(N) 且带 @slow 标记:N 是所有源集合的元素总数。它是同步命令,大集合上会阻塞主线程,必要时在副本(不能写)之外的低峰时段执行,或拆小批量。

写入成本真实存在:结果集合要实际占用内存。把 SUNIONSTORE 当作"每次请求都跑一遍"的实时计算是误用,它适合"定期预计算 + 多次读取"。

覆盖会导致内存抖动:旧结果集合被整体释放、新集合被整体构建,内存峰值约为新旧两份之和。超大集合上要留意 maxmemory 触发淘汰的可能。

TTL 必须显式设置:覆盖之后 TTL 归零,这在缓存场景里是"内存缓慢泄漏"的常见来源。写完后立刻 EXPIRE,或把"生成 + 过期"收进 Lua。

避免用自引用反复重建:SUNIONSTORE dest dest src 每次都重建整个集合,成本 O(总元素数)。持续累加应使用 SADD dest member...(O(每次新增数量))。

与 SSCAN 配合读取:结果集合可能很大,读取时不要用 SMEMBERS 拉全量,用 SSCAN 分批遍历。

十一、典型业务场景

权限并集缓存:用户多角色权限集合求并集写入 perm:{uid},并设置 30 分钟 TTL,登录后的鉴权直接读缓存集合。

多来源名单合并:把站内关注、导入名单、活动参与三个集合合并为 audience:{campaign},投放系统读取结果集合。

标签画像预计算:多个标签集合定期求并集写入画像 Key,供离线任务与实时查询共用。

季度/年度活跃汇总:按月活跃用户集合求并集写入季度活跃集合,报表直接 SCARD。

多仓库库存合并:各仓可售 ID 集合求并集写入全局可售集合,前端查询只读一份数据。

灰度名单合并:多批次灰度名单求并集写入最终名单,避免每次请求都做多集合运算。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/vbhfdghff/article/details/169557139

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--