玩转 Redis · Set 篇
环境 :Docker 容器
redis-learn(Redis 7.4.11) 本篇地位 :五大类型 4/5。一句话:自动去重、无序的集合
0. 一分钟回顾
Set 可以理解为一个"自动去重的袋子":往里面放相同的元素,Redis 只保留一份;但它不保证元素的顺序。
text
user:1:tags → { redis, database }
Set 不是 List:
- List 有顺序,可以按下标访问;Set 无序,不能依赖返回顺序。
- List 允许重复元素;Set 自动去重。
- List 擅长两端进出;Set 擅长成员判断和集合运算。
典型场景:用户标签、去重、共同好友、共同兴趣、权限集合、集合差异比较。
1. 命令全家桶
| 操作 | 命令 | 一句话说明 |
|---|---|---|
| 增 | SADD key member [member ...] |
添加成员,重复成员自动忽略;返回实际新增数量 |
| 查 | SMEMBERS key |
查看全部成员;顺序不固定 |
| 查 | SISMEMBER key member |
判断成员是否存在;返回 1/0 |
| 查 | SCARD key |
查看成员数量 |
| 删 | SREM key member [member ...] |
按值删除成员;返回实际删除数量 |
| 查 | SINTER key [key ...] |
求交集:多个集合共同拥有的成员 |
| 查 | SUNION key [key ...] |
求并集:多个集合全部成员去重合并 |
| 查 | SDIFF key [key ...] |
求差集:第一个集合有、后续集合没有 |
| 查 | SRANDMEMBER key [count] |
随机查看成员,不删除 |
| 删 | SPOP key [count] |
随机取出并删除成员 |
| 改 | SMOVE source destination member |
把成员从一个 Set 移动到另一个 Set |
| 删 | DEL key |
删除整个 Set(通用命令) |
2. 返回值与核心规律
SADD返回实际新增成员数量,不是传入参数数量。SISMEMBER返回1表示存在,0表示不存在。SCARD返回 Set 成员数量,类似 List 的LLEN。SREM返回实际删除成员数量。SMOVE返回1表示移动成功,0表示源集合中没有该成员。SPOP返回被随机取出的成员;如果集合空了,最后一个成员删除后 key 自动消失。SMEMBERS、SINTER、SUNION、SDIFF的返回顺序都不应作为业务依据。- key 必须完全一致:
user:2:tags和user:2:tags:是两个不同的 key。
3. 实操流水(真实命令 + 结果)
3.1 添加成员与自动去重:SADD
text
127.0.0.1:6379> SADD user:1:tags redis
1
127.0.0.1:6379> SADD user:1:tags redis "database"
1
第二次传入了两个成员,但 redis 已经存在,只有 database 是新增成员,所以返回 1。
3.2 查看全部成员:SMEMBERS
text
127.0.0.1:6379> SMEMBERS user:1:tags
redis
database
集合当前有 redis 和 database。Set 无序,返回顺序不固定,不能依赖本次顺序。
3.3 判断成员与统计数量:SISMEMBER、SCARD
text
127.0.0.1:6379> SISMEMBER user:1:tags database
1
127.0.0.1:6379> SISMEMBER user:1:tags "docker"
0
127.0.0.1:6379> SCARD user:1:tags
2
database 存在,返回 1;docker 不存在,返回 0;集合共有两个成员。
3.4 按值删除:SREM
text
127.0.0.1:6379> SREM user:1:tags "database"
1
127.0.0.1:6379> SMEMBERS user:1:tags
redis
SREM 返回 1,表示成功删除 database。
3.5 集合运算:交集、并集、差集
重新创建第二个用户的标签集合:
text
127.0.0.1:6379> SADD user:2:tags "database" "linux" "redis"
3
求两个用户共同拥有的标签:
text
127.0.0.1:6379> SINTER user:1:tags user:2:tags
redis
求两个集合全部不重复成员:
text
127.0.0.1:6379> SUNION user:1:tags user:2:tags
redis
database
linux
差集的方向很重要。第一次查询用户 1 独有的标签:
text
127.0.0.1:6379> SDIFF user:1:tags user:2:tags
没有输出,表示用户 1 有、用户 2 没有的标签不存在。
反过来查询用户 2 独有的标签:
text
127.0.0.1:6379> SDIFF user:2:tags user:1:tags
database
linux
SDIFF A B 的含义是:A 有、B 没有。
3.6 随机查看与随机取出:SRANDMEMBER、SPOP
随机查看成员:
text
127.0.0.1:6379> SRANDMEMBER user:2:tags
redis
127.0.0.1:6379> SRANDMEMBER user:2:tags
redis
127.0.0.1:6379> SRANDMEMBER user:2:tags
linux
结果可以重复,因为 SRANDMEMBER 只查看,不删除成员。
之后使用 SPOP 随机取出并删除成员:
text
127.0.0.1:6379> SPOP user:2:tags
database
127.0.0.1:6379> SMEMBERS user:2:tags
redis
127.0.0.1:6379> SPOP user:2:tags
redis
127.0.0.1:6379> SMEMBERS user:2:tags
第二次 SMEMBERS 没有输出,说明最后一个成员也被删除。
验证 key:
text
127.0.0.1:6379> EXISTS user:2:tags
0
3.7 移动成员:SMOVE
重新准备两个集合:
text
127.0.0.1:6379> SADD user:1:tags "redis" "docker"
1
这里 redis 原本已经存在,所以只新增了 docker。
练习中曾误写了带末尾冒号的 key:
text
127.0.0.1:6379> SADD user:2:tags: "docker"
1
127.0.0.1:6379> SADD user:2:tags: "linux"
1
user:2:tags: 与正确的 user:2:tags 不同,后来在正确 key 上补入 linux:
text
127.0.0.1:6379> SADD user:2:tags "linux"
1
把 docker 从用户 1 移到用户 2:
text
127.0.0.1:6379> SMOVE user:1:tags user:2:tags "docker"
1
127.0.0.1:6379> SMEMBERS user:2:tags
linux
docker
127.0.0.1:6379> SMEMBERS user:1:tags
redis
SMOVE 是真正的移动,不是复制:源集合中的 docker 已消失,目标集合中出现了 docker。
清理误创建的临时 key:
text
127.0.0.1:6379> DEL user:2:tags:
1
127.0.0.1:6379> EXISTS user:2:tags:
0
这里最后的 EXISTS user:2:tags 返回 0,验证的是误创建的带末尾冒号的 key 已清理;正确的 user:2:tags 仍然存在。
4. 踩坑实录
| 坑 | 现象 | 教训 |
|---|---|---|
| 把 Set 当有序数组 | 以为 SMEMBERS 每次顺序固定 |
Set 无序,不能依赖成员返回顺序 |
| 重复添加成员 | SADD 传入两个值但返回 1 |
返回的是实际新增数量,重复成员会被忽略 |
| 差集方向弄反 | SDIFF user:1:tags user:2:tags 没结果 |
SDIFF A B 是 A 有、B 没有 |
混淆 SRANDMEMBER 和 SPOP |
以为随机查看会删除 | SRANDMEMBER 只看;SPOP 取出并删 |
| key 末尾多冒号 | user:2:tags: 查询不到 user:2:tags 的内容 |
冒号也是 key 的一部分,拼写必须完全一致 |
| 误看空集合 | SMEMBERS 没输出 |
可能是 key 不存在或集合为空,用 EXISTS 继续确认 |
5. 典型场景
5.1 用户标签去重
redis
SADD user:1:tags redis docker database
SISMEMBER user:1:tags redis
SMEMBERS user:1:tags
同一个标签多次添加也只保留一份。
5.2 共同兴趣或共同好友
redis
SINTER user:1:tags user:2:tags
交集就是两个人共同拥有的标签或共同好友。
5.3 权限或功能集合差异
redis
SDIFF user:2:permissions user:1:permissions
可以找出一个用户多出来的权限。
5.4 随机抽奖
redis
SPOP lottery:participants
抽出成员并删除,适合"不重复中奖"的简单抽奖。若只想随机看看,不改变候选集合,使用 SRANDMEMBER。
6. 核心记忆
Set 是自动去重、无序的集合,适合标签、去重、共同好友和集合运算。
text
SADD 加成员并自动去重
SMEMBERS 看全部成员
SISMEMBER 判断成员是否存在
SCARD 看成员数量
SREM 按值删除
SINTER 求共同成员
SUNION 合并全部不重复成员
SDIFF 求第一个集合独有成员
SRANDMEMBER 随机看,不删除
SPOP 随机取出并删除
SMOVE 在两个集合之间移动
记忆画面:
text
Set = 一个不允许重复、没有固定顺序的集合袋子
7. 自检清单
- Set 和 List 在顺序、重复元素方面有什么区别?
-
SADD返回的是传入数量还是实际新增数量? -
SMEMBERS的返回顺序能不能依赖? -
SISMEMBER返回1和0分别代表什么? -
SCARD统计什么? -
SREM和SPOP的删除方式有什么区别? -
SRANDMEMBER会不会删除成员? -
SINTER、SUNION、SDIFF分别是什么? -
SDIFF A B的方向如何理解? -
SMOVE是复制还是移动? -
user:2:tags和user:2:tags:是同一个 key 吗? - Set 最后一个成员被删除后,key 会怎样?
8. 类型地图(4/5)
| 篇章 | 形状 | 典型用途 | 招牌命令 | 状态 |
|---|---|---|---|---|
| 📗 String | 一行字的纸条 | 缓存、计数器 | SET GET INCR |
✅ |
| 📘 Hash | 小表格(字段↔值) | 对象、单字段修改、购物车 | HSET HGET HINCRBY |
✅ |
| 📙 List | 有顺序的双向链表 | 消息队列、最新列表 | LPUSH RPOP LRANGE |
✅ |
| 📕 Set | 自动去重的无序集合 | 标签、去重、共同好友 | SADD SINTER SMEMBERS |
✅ |
| 📓 ZSet | 带分数自动排队 | 排行榜、优先级 | ZADD ZRANGE |
⬅️ 下一站 |