Redis SINTERSTORE 命令详细教程
SINTERSTORE计算多个集合的交集,并把结果写入指定的目标 Key,返回写入的成员数量。它是SINTER的写版本:SINTER 把交集返回给客户端,SINTERSTORE 把交集留在 Redis 里,便于缓存、后续运算或分页读取。命令从 Redis 1.0.0 起提供,目标 Key 会被完全覆盖,结果为空时目标 Key 会被删除。
资料合集:https://pan.quark.cn/s/10e98d308913、https://pan.quark.cn/s/f56bc69c5338
一、概览与语法
SINTERSTORE destination key [key ...]| 项目 | 说明 |
|---|---|
| 数据类型 | Set |
| 支持版本 | Redis 1.0.0 起 |
| destination | 结果写入的目标 Key,已存在时被覆盖 |
| key | 至少一个参与运算的集合,全部参与求交 |
| 返回值 | 整数,写入目标集合的成员数量;结果为空时返回 0 |
| 时间复杂度 | O(N×M) 最坏情况,N 为最小集合成员数,M 为集合个数 |
| ACL | @write、@set、@slow |
| 命令标记 | write、denyoom |
官方说明:计算所有指定集合的交集,并把结果存储在 destination 中。若 destination 已存在,则被覆盖。$TRAE_REF
二、基础示例
以下命令在测试实例的 redis-cli 中执行。文中结果是预期说明,未实际连接 Redis 运行。示例沿用官方文档的数据与结果。
DEL tutorial:{sinterstore}:key1 tutorial:{sinterstore}:key2 tutorial:{sinterstore}:key SADD tutorial:{sinterstore}:key1 "a" "b" "c" SADD tutorial:{sinterstore}:key2 "c" "d" "e" SINTERSTORE tutorial:{sinterstore}:key tutorial:{sinterstore}:key1 tutorial:{sinterstore}:key2 SCARD tutorial:{sinterstore}:key SMEMBERS tutorial:{sinterstore}:key预期结果:两条 SADD 各返回3;SINTERSTORE 返回1;SCARD 返回1;SMEMBERS 返回"c"。只有c同时存在于两个源集合。
三、destination 的覆盖与删除
这是本命令最容易出问题的行为:
| destination 原状态 | 运算后 |
|---|---|
| 不存在 | 创建并写入结果 |
| 已存在,结果非空 | 旧内容被整体覆盖,旧成员全部丢失 |
| 已存在,结果为空 | destination 被删除,Key 不再存在 |
| 是其他数据类型 | 被覆盖为 Set |
DEL tutorial:{sinterstore}:overwrite tutorial:{sinterstore}:s1 tutorial:{sinterstore}:s2 SADD tutorial:{sinterstore}:overwrite "old1" "old2" SADD tutorial:{sinterstore}:s1 "x" "y" SADD tutorial:{sinterstore}:s2 "y" "z" SINTERSTORE tutorial:{sinterstore}:overwrite tutorial:{sinterstore}:s1 tutorial:{sinterstore}:s2 SMEMBERS tutorial:{sinterstore}:overwrite SCARD tutorial:{sinterstore}:overwrite预期结果:SINTERSTORE 返回1;SMEMBERS 只返回"y",旧的old1、old2已被清除;SCARD 返回1。
覆盖是"replace"而不是"merge"。如果 destination 之前存有业务数据,一次误调用就会永久丢失,务必使用独立的结果 Key。
四、空结果的删除语义
当交集为空时,destination 不是变成"空集合",而是 Key 整体消失:
DEL tutorial:{sinterstore}:empty SADD tutorial:{sinterstore}:a1 "x" SADD tutorial:{sinterstore}:a2 "y" SINTERSTORE tutorial:{sinterstore}:empty tutorial:{sinterstore}:a1 tutorial:{sinterstore}:a2 EXISTS tutorial:{sinterstore}:empty SCARD tutorial:{sinterstore}:empty预期结果:SINTERSTORE 返回0;EXISTS 返回0;SCARD 返回0。三者一致说明 Key 已被删除。
这一行为对缓存场景很关键:缓存 Key 消失后,读取方看到的是"不存在",需要用 SCARD 或 EXISTS 判断,而不能假设"返回空数组即缓存命中但结果为空"。
五、destination 参与运算
destination 可以与源 Key 同名,Redis 会先完整计算再写入,不会出现边算边改的问题:
DEL tutorial:{sinterstore}:self SADD tutorial:{sinterstore}:self "a" "b" SINTERSTORE tutorial:{sinterstore}:self tutorial:{sinterstore}:self SMEMBERS tutorial:{sinterstore}:self预期结果:SINTERSTORE 返回2;SMEMBERS 返回a、b。只传一个 Key 时交集就是它自己。
虽然语义正确,但这种写法可读性差,容易让维护者误以为发生了自引用错误。建议把结果写到带:result后缀的独立 Key。
六、边界情况与错误处理
| 场景 | 行为 |
|---|---|
| 只传 destination 不传 key | 报语法错误 |
| 只传一个 key | 返回该集合全部成员,等价于复制 |
| 任一 Key 不存在 | 交集为空,destination 被删除,返回 0 |
| 任一 Key 类型不是 Set | 报 WRONGTYPE,destination 不被修改 |
| destination 是其他类型 | 被覆盖为 Set,不报错 |
SET tutorial:{sinterstore}:str text SINTERSTORE tutorial:{sinterstore}:dst tutorial:{sinterstore}:key1 tutorial:{sinterstore}:str SINTERSTORE tutorial:{sinterstore}:dst2预期结果:第一条因参与运算的 str 是 String 类型报 WRONGTYPE,dst 不会被创建;第二条缺少参与运算的 key,报ERR wrong number of arguments for 'sinterstore' command。
七、与相近命令的区别
| 命令 | 结果去向 | 是否覆盖目标 | 是否传输成员 | 返回 |
|---|---|---|---|---|
| SINTERSTORE | 写入 destination | 是 | 否 | 写入数量 |
| SINTER | 只返回客户端 | 无 | 是 | 成员数组 |
| SINTERCARD | 只返回数量 | 无 | 否 | 整数 |
| SDIFFSTORE | 差集写入 destination | 是 | 否 | 写入数量 |
| SUNIONSTORE | 并集写入 destination | 是 | 否 | 写入数量 |
| SMEMBERS | 读取单个集合 | 无 | 是 | 成员数组 |
三兄弟的选择:需要成员且本次就用,SINTER;只需要数量,SINTERCARD;结果要落地复用,SINTERSTORE。
八、客户端示例
前提为已安装 redis-py 并准备好本地测试实例。redis-py 的sinterstore(dest, keys, *args)返回写入数量。
importredis r=redis.Redis(host="localhost",port=6379,decode_responses=True)k1="tutorial:{sinterstore}:py1"k2="tutorial:{sinterstore}:py2"dest="tutorial:{sinterstore}:pydest"try:r.delete(k1,k2,dest)r.sadd(k1,"a","b","c")r.sadd(k2,"c","d","e")print(r.sinterstore(dest,[k1,k2]))# 1print(r.smembers(dest))# {'c'}r.srem(k2,"c")print(r.sinterstore(dest,[k1,k2]))# 0,交集为空print(r.exists(dest))# 0,目标被删除finally:r.delete(k1,k2,dest)r.close()Java(Jedis)示例,sinterStore(dest, keys...)返回long:
try(Jedisjedis=newJedis("localhost",6379)){Stringk1="tutorial:{sinterstore}:java1";Stringk2="tutorial:{sinterstore}:java2";Stringdest="tutorial:{sinterstore}:javadest";jedis.del(k1,k2,dest);jedis.sadd(k1,"a","b","c");jedis.sadd(k2,"c","d","e");System.out.println(jedis.sinterStore(dest,k1,k2));// 1System.out.println(jedis.smembers(dest));// [c]jedis.del(k1,k2,dest);}九、原子性与并发
单命令原子:计算与写入在同一条命令中完成,其他客户端看不到"算完但未写"的中间状态。
并发覆盖:多个生产者向同一 destination 执行 SINTERSTORE 时,最终内容取决于执行顺序,后执行者完全覆盖先执行者。不要把同一个 destination 当作共享累加器。
结果非快照:命令结束后 destination 仍是普通集合,可被后续 SADD/SREM 修改,也可被 EXPIRE 设置过期。若希望结果"生成即固定",需在写入后立即设置 TTL 或限制写入权限。
集群限制:Redis Cluster 中 destination 与所有参与运算的 Key 必须落在同一哈希槽,否则报 CROSSSLOT。多个 Key 加相同哈希标签即可保证同槽。
十、性能与使用注意事项
复杂度 O(N×M):N 是最小集合的成员数,M 是集合个数。Redis 会选择最小集合为起点遍历,再对每个成员做存在性检查。
比 SINTER 多一次写入成本:结果需要实际存储,占用内存。若只是临时用一次,用 SINTER 更合适。
避免"取回再写回":客户端先 SINTER 再 SADD 需要两次往返,且两次之间数据可能变化;SINTERSTORE 在服务端一次原子完成,既省往返也避免竞态。
预计算缓存的典型用法:数据变更时触发 SINTERSTORE 写入缓存集合,并紧接EXPIRE;读取方直接用 SCARD/SMEMBERS,避免每次请求都做交集运算。
大集合仍会阻塞:带@slow标记,必要时在副本执行或离线批处理。
十一、典型业务场景
共同好友缓存:把两个用户关注集合的交集写入common:{a}:{b}并设短 TTL,前端翻页读缓存,避免重复求交。
多标签筛选中台:把"颜色 ∩ 尺寸 ∩ 品牌"的交集写入筛选结果集合,供商品列表分页读取。分页场景下这个模式比 SINTER + 客户端分页更稳,因为结果集合已固定。
权限交集校验:用户角色集合与接口要求集合求交后落库到临时 Key,供审计查询。
同时在线用户快照:多个业务线在线集合求交后写入快照 Key,供大屏展示。
推荐候选交集:把候选集与可用集求交后写入eligible:{scene},线上服务直接消费。