玩转 Redis · Set 篇
环境:Docker 容器
redis-learn(Redis 7.4.11) 本篇地位:五大类型 4/5。一句话:自动去重、无序的集合
0. 一分钟回顾
Set 可以理解为一个“自动去重的袋子”:往里面放相同的元素,Redis 只保留一份;但它不保证元素的顺序。
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
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
127.0.0.1:6379> SMEMBERS user:1:tags
redis
database
集合当前有 redis 和 database。Set 无序,返回顺序不固定,不能依赖本次顺序。
3.3 判断成员与统计数量:SISMEMBER、SCARD
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
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 集合运算:交集、并集、差集
重新创建第二个用户的标签集合:
127.0.0.1:6379> SADD user:2:tags "database" "linux" "redis"
3
求两个用户共同拥有的标签:
127.0.0.1:6379> SINTER user:1:tags user:2:tags
redis
求两个集合全部不重复成员:
127.0.0.1:6379> SUNION user:1:tags user:2:tags
redis
database
linux
差集的方向很重要。第一次查询用户 1 独有的标签:
127.0.0.1:6379> SDIFF user:1:tags user:2:tags
没有输出,表示用户 1 有、用户 2 没有的标签不存在。
反过来查询用户 2 独有的标签:
127.0.0.1:6379> SDIFF user:2:tags user:1:tags
database
linux
SDIFF A B 的含义是:A 有、B 没有。
3.6 随机查看与随机取出:SRANDMEMBER、SPOP
随机查看成员:
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 随机取出并删除成员:
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:
127.0.0.1:6379> EXISTS user:2:tags
0
3.7 移动成员:SMOVE
重新准备两个集合:
127.0.0.1:6379> SADD user:1:tags "redis" "docker"
1
这里 redis 原本已经存在,所以只新增了 docker。
练习中曾误写了带末尾冒号的 key:
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:
127.0.0.1:6379> SADD user:2:tags "linux"
1
把 docker 从用户 1 移到用户 2:
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:
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 用户标签去重
SADD user:1:tags redis docker database
SISMEMBER user:1:tags redis
SMEMBERS user:1:tags
同一个标签多次添加也只保留一份。
5.2 共同兴趣或共同好友
SINTER user:1:tags user:2:tags
交集就是两个人共同拥有的标签或共同好友。
5.3 权限或功能集合差异
SDIFF user:2:permissions user:1:permissions
可以找出一个用户多出来的权限。
5.4 随机抽奖
SPOP lottery:participants
抽出成员并删除,适合“不重复中奖”的简单抽奖。若只想随机看看,不改变候选集合,使用 SRANDMEMBER。
6. 核心记忆
Set 是自动去重、无序的集合,适合标签、去重、共同好友和集合运算。
SADD 加成员并自动去重
SMEMBERS 看全部成员
SISMEMBER 判断成员是否存在
SCARD 看成员数量
SREM 按值删除
SINTER 求共同成员
SUNION 合并全部不重复成员
SDIFF 求第一个集合独有成员
SRANDMEMBER 随机看,不删除
SPOP 随机取出并删除
SMOVE 在两个集合之间移动
记忆画面:
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 | ⬅️ 下一站 |