【计算基础|Redis02】【上手】五大数据类型命令逐条实操
上一篇把连接、配置、通用 key 命令跑通了。这一篇是 Redis 的核心------五大数据类型 。很多人学 Redis 卡在"命令背了一堆,不知道啥时候用哪个"。这篇反过来:每个类型先讲它长什么样、适合干啥,再把高频命令逐条过一遍(语法→实例→输出→易错点),最后用五个真实业务场景把它串起来。
所有命令在 Redis 7.4.11 真实实例上跑过,输出原样贴入。
环境:Docker 官方镜像
redis:7(redis-cli 7.4.11)。先docker exec -it <容器名> redis-cli连进去跟着敲。
一、先厘清:五种类型到底是五种"数据结构"
Redis 的 value 不是只能存字符串。它给你五种现成的数据结构,选对类型,很多业务逻辑一行命令就解决。一句话先记住:
| 类型 | 像什么 | 典型场景 |
|---|---|---|
| String | 一个 key → 一个值 | 缓存、计数、分布式锁、会话 |
| List | 双向链表 | 消息队列、最新列表、时间线 |
| Hash | 对象(field-value 字典) | 用户信息、商品属性 |
| Set | 无序、不重复集合 | 标签、去重、共同好友 |
| ZSet | 带分数的 Set(自动排序) | 排行榜、延迟队列、限流 |
🔴 选型口诀:要计数/缓存用 String,要排队用 List,要存对象用 Hash,要去重/交并差用 Set,要排序用 ZSet。
二、String:最基础,也最能打
String 是最朴素的类型,一个 key 对应一个 value。⚠️ 单个 value 最大 512MB(虽然没人真塞这么大)。它远不止"存个字符串",因为 Redis 给它配了原子的数字操作。
2.1 命令逐条
| 命令 | 语法 | 作用 | 易错点 |
|---|---|---|---|
| SET / GET | SET k v / GET k |
写 / 读 | --- |
| SETNX | SETNX k v |
不存在才设 | 返回 1 成功 / 0 已存在;分布式锁的基础 |
| SETEX | SETEX k 秒 v |
设值+过期原子完成 | 一步到位,别先 SET 再 EXPIRE |
| MSET / MGET | MSET k1 v1 k2 v2 |
批量写 / 批量读 | 一次网络往返,省 RTT |
| INCR / DECR | INCR k / DECR k |
原子 ±1 | ⚠️ 值不是整数会报错 |
| INCRBY / DECRBY | INCRBY k n |
原子 ±n | --- |
| APPEND | APPEND k v |
尾部追加 | --- |
| STRLEN | STRLEN k |
值长度(字节) | 中文按字节算 |
| GETRANGE / SETRANGE | GETRANGE k a b |
取/改子串 | 下标从 0,闭区间 |
| GETSET | GETSET k v |
设新值,返回旧值 | 原子"换值取旧" |
实跑:
bash
# 基本读写
SET goods:1001:stock 50
GET goods:1001:stock
text
OK
50
SETNX(分布式锁基础------第二个加锁的人失败):
bash
SETNX lock:order "u1" # 第一次,key 不存在 → 1
SETNX lock:order "u2" # 第二次,key 已存在 → 0
text
1
0
SETEX(设值+过期一步原子完成):
bash
SETEX code:138001 60 "889900"
TTL code:138001
text
OK
60
MSET / MGET(一次往返读写多个):
bash
MSET k1 v1 k2 v2 k3 v3
MGET k1 k2 k3 notexist
text
OK
v1
v2
v3
# notexist 那行是空(nil)
INCR 系列(原子自增,计数神器):
bash
SET pv 100
INCR pv # 101
INCRBY pv 20 # 121
DECR pv # 120
text
OK
101
121
120
⚠️ INCR 对非整数直接报错:
bash
SET name "alice"
INCR name
text
OK
ERR value is not an integer or out of range
APPEND / STRLEN / GETSET:
bash
SET greet "hello"
APPEND greet " world" # 追加后长度 11
STRLEN greet
GETSET greet "reset" # 返回旧值 "hello world"
GET greet # 现在是 "reset"
text
OK
11
11
hello world
reset
GETRANGE / SETRANGE:
bash
SET book "redis is fast"
GETRANGE book 0 4 # redis
SETRANGE book 9 "SLOW" # 从第9位开始替换
GET book # redis is SLOW
text
OK
redis
13
redis is SLOW
2.2 场景:商品库存扣减
bash
SET stock:1001 100 # 上架 100 件
DECR stock:1001 # 卖一件 → 99
INCRBY stock:1001 5 # 退货 5 件 → 104
🔴 为什么用 INCR/DECR 而不是读出来改完写回去?因为 INCR 是原子的。并发 1000 个请求扣库存,读-改-写会超卖,INCR 不会。
三、List:双向链表,天然的队列/栈
List 是按插入顺序排序的字符串链表,头尾插入/删除是 O(1),中间是 O(N)(底层是 quicklist)。所以它适合"只从两端操作"的场景。
3.1 命令逐条
| 命令 | 语法 | 作用 | 易错点 |
|---|---|---|---|
| LPUSH / RPUSH | LPUSH k v |
从左 / 右插入 | LPUSH 后左边是队头 |
| LPOP / RPOP | LPOP k |
从左 / 右弹出 | 返回弹出的值 |
| LRANGE | LRANGE k start stop |
取区间元素 | ⚠️ stop 含边界 ;0 -1 是全部 |
| LLEN | LLEN k |
长度 | --- |
| LINDEX | LINDEX k i |
按下标取 | O(N),别频繁用 |
| LSET | LSET k i v |
按下标改 | --- |
| LINSERT | LINSERT k BEFORE/AFTER 锚 v |
在锚点附近插 | --- |
| LREM | LREM k count v |
删等于 v 的元素 | count 控制删几个、方向 |
| LTRIM | LTRIM k start stop |
只保留区间,删其余 | ⚠️ LTRIM 0 99 留最新 100 条 |
实跑(当消息队列:LPUSH 生产、RPOP 消费):
bash
LPUSH queue:msg "task-1" "task-2" "task-3"
LRANGE queue:msg 0 -1
LLEN queue:msg
LINDEX queue:msg 0
text
3
task-3 # LPUSH 是左插,最后插的在最左
task-2
task-1
3
task-3
RPOP 从右消费(先进先出):
bash
RPOP queue:msg # task-1(最早进的先出)
LRANGE queue:msg 0 -1
text
task-1
task-3
task-2
LSET / LINSERT:
bash
LSET queue:msg 0 "task-2-updated"
LINSERT queue:msg BEFORE "task-2-updated" "task-0"
LRANGE queue:msg 0 -1
text
OK
3
task-0
task-2-updated
task-2
LREM(删指定元素)/ LTRIM(裁剪):
bash
RPUSH queue:msg "task-9" "task-9" "task-9"
LREM queue:msg 2 "task-9" # 从左往右删 2 个 task-9
LRANGE queue:msg 0 -1
LTRIM queue:msg 0 2 # 只留前 3 个
LRANGE queue:msg 0 -1
LLEN queue:msg
text
2
task-0
task-2-updated
task-2
task-9 # 还剩 1 个 task-9
OK
task-0
task-2-updated
task-2
3
3.2 场景:简单消息队列 + 最新 100 条
bash
# 生产:LPUSH 塞任务;消费:RPOP 取任务(FIFO)
LPUSH email:queue "uid=1;to=a@x.com"
RPOP email:queue
# "最新 100 条":LPUSH 进,再 LTRIM 裁到留 100 条
LPUSH feed:me "post-1001"
LTRIM feed:me 0 99
⚠️ List 做队列不支持阻塞 ,消费者得自己轮询 RPOP 空转。要阻塞等消息用 BRPOP,要可靠消息用 Stream。
四、Hash:存对象,别用一大坨 String
Hash 是 key 下面挂一堆 field-value,天然就是"对象"。比如一个用户,不用把整个对象序列化成一个 String,而是每个字段一个 field。
4.1 命令逐条
| 命令 | 语法 | 作用 | 易错点 |
|---|---|---|---|
| HSET / HGET | HSET k f v / HGET k f |
设/取单个字段 | 7.x 多字段用 HSET |
| HMGET | HMGET k f1 f2 |
批量取字段 | --- |
| HDEL | HDEL k f |
删字段 | --- |
| HEXISTS | HEXISTS k f |
字段是否存在 | 1/0 |
| HGETALL | HGETALL k |
取全部字段和值 | ⚠️ 大 hash 别用,O(N) |
| HKEYS / HVALS | HKEYS k |
只取所有字段名 / 值 | --- |
| HLEN | HLEN k |
字段个数 | --- |
| HINCRBY | HINCRBY k f n |
字段原子加 n | --- |
| HSCAN | HSCAN k cursor |
增量遍历字段 | 替代 HGETALL 大数据量 |
Redis 6.0 起
HMSET已废弃,多字段写入统一用HSET k f1 v1 f2 v2。
实跑(存用户对象):
bash
HSET user:100 name "alice" age 28 city "Beijing"
HGET user:100 name
HGETALL user:100
text
3
alice
name
alice
age
28
city
Beijing
HEXISTS / HKEYS / HVALS / HLEN:
bash
HEXISTS user:100 phone # 0
HEXISTS user:100 name # 1
HKEYS user:100
HVALS user:100
HLEN user:100
text
0
1
name
age
city
alice
28
Beijing
3
HINCRBY(字段级自增)/ HDEL:
bash
HSET user:100 score 10
HINCRBY user:100 score 5 # 15
HDEL user:100 city # 删掉 city 字段
HGET user:100 city # 空
text
1
15
1
4.2 场景:用户信息
bash
HSET user:1 name "alice" age 28 city "Beijing"
HGET user:1 name # 单独改/取某个字段,不用动整个对象
HINCRBY user:1 age 1 # 长一岁
🔴 什么时候用 Hash、什么时候用 String 存 JSON?字段经常独立改用 Hash;整个对象整体读写、字段多而杂用 String 存 JSON(序列化方便)。
五、Set:无序不重复,交并差是杀手锏
Set 里的元素不重复、无序 。它真正香的不是"存个集合",而是集合运算:交集、并集、差集,一行命令搞定。
5.1 命令逐条
| 命令 | 语法 | 作用 | 易错点 |
|---|---|---|---|
| SADD / SREM | SADD k v... |
加 / 删元素 | 重复元素自动去重 |
| SMEMBERS | SMEMBERS k |
取全部元素 | ⚠️ 大 set 慎用 |
| SISMEMBER | SISMEMBER k v |
是否存在 | 1/0,O(1) |
| SCARD | SCARD k |
元素个数 | --- |
| SPOP | SPOP k |
随机弹出并删 | 抽奖 |
| SRANDMEMBER | SRANDMEMBER k n |
随机取 n 个不删 | --- |
| SINTER | SINTER k1 k2 |
交集 | 共同好友 |
| SUNION | SUNION k1 k2 |
并集 | --- |
| SDIFF | SDIFF k1 k2 |
差集 | 我有你没有 |
| SMOVE | SMOVE k1 k2 v |
把 v 从 k1 移到 k2 | --- |
实跑(两个人的关注列表):
bash
SADD follow:a "u1" "u2" "u3" "u4"
SADD follow:b "u3" "u4" "u5" "u6"
SMEMBERS follow:a
text
4
4
u1
u2
u3
u4
SISMEMBER / SCARD:
bash
SISMEMBER follow:a "u3" # 1
SISMEMBER follow:a "u99" # 0
SCARD follow:a # 4
text
1
0
4
交并差(本篇灵魂):
bash
SINTER follow:a follow:b # 共同关注
SUNION follow:a follow:b # 全部关注过的人
SDIFF follow:a follow:b # A 关注但 B 没关注的
text
# 交集
u3
u4
# 并集
u1
u2
u3
u4
u5
u6
# 差集
u1
u2
SPOP / SRANDMEMBER / SMOVE / SREM:
bash
SPOP follow:b # 随机弹一个
SRANDMEMBER follow:a 2 # 随机取 2 个
SMOVE follow:a follow:b "u2" # 把 u2 从 a 挪到 b
SREM follow:b "u5" # 删 u5
text
u3
u3
u4
1
1
5.2 场景:共同好友 / 标签去重
bash
# 两个人共同关注谁?一行交集
SINTER follow:a follow:b
# 文章打标签,自动去重
SADD article:100:tags "redis" "db" "cache"
SISMEMBER article:100:tags "redis"
六、ZSet:带分数的 Set,排行榜本命
ZSet(Sorted Set)每个 member 绑一个 score(分数) ,member 唯一、score 可重复,并且自动按 score 排好序。要排序、要 Top N,首选它。
6.1 命令逐条
| 命令 | 语法 | 作用 | 易错点 |
|---|---|---|---|
| ZADD | ZADD k score member |
加元素 | member 重复则更新 score |
| ZRANGE | ZRANGE k s e [WITHSCORES] |
按 score 升序取区间 | 下标,闭区间 |
| ZREVRANGE | ZREVRANGE k s e |
按 score 降序 | 排行榜 Top N |
| ZRANGEBYSCORE | ZRANGEBYSCORE k min max |
按分数区间取 | (80 表示开区间 |
| ZSCORE | ZSCORE k member |
取某 member 的分 | --- |
| ZCARD | ZCARD k |
元素总数 | --- |
| ZCOUNT | ZCOUNT k min max |
区间内个数 | --- |
| ZINCRBY | ZINCRBY k n member |
给某成员加分 | 原子 |
| ZRANK / ZREVRANK | ZRANK k member |
排名(升/降) | 从 0 开始 |
| ZREM | ZREM k member |
删成员 | --- |
实跑(积分排行榜):
bash
ZADD rank 90 "alice" 85 "bob" 95 "carol" 70 "dave"
ZRANGE rank 0 -1 WITHSCORES # 升序,带分数
text
4
dave
70
bob
85
alice
90
carol
95
降序取 Top3:
bash
ZREVRANGE rank 0 2 WITHSCORES
text
carol
95
alice
90
bob
85
按分数区间取(含边界):
bash
ZRANGEBYSCORE rank 80 100
text
bob
alice
carol
开区间(( 表示不含该边界):
bash
ZRANGEBYSCORE rank "(80" 100 # 分数严格大于 80
text
bob
alice
carol
ZSCORE / ZCARD / ZCOUNT / ZINCRBY:
bash
ZSCORE rank alice # 90
ZCARD rank # 4
ZCOUNT rank 80 100 # 3 个在 [80,100]
ZINCRBY rank 5 alice # alice 加 5 分 → 95
ZSCORE rank alice
text
90
4
3
95
95
ZRANK / ZREVRANK(名次从 0 开始,别当成第 1 名):
bash
ZREVRANK rank carol # carol 降序排第 0(即第一名)
ZRANK rank dave # dave 升序排第 0(最后一名)
text
0
0
ZREM:
bash
ZREM rank dave
ZCARD rank # 4 → 3
text
1
3
6.2 场景:积分榜 / 延迟队列
bash
# 用户答题积分,每答一题加 10 分
ZINCRBY game:score 10 "alice"
# 实时 Top10
ZREVRANGE game:score 0 9 WITHSCORES
# 延迟队列:把"执行时间戳"当 score,到点的任务 ZRANGEBYSCORE 捞出来
ZADD delay:queue 1737000000 "send-sms-123"
🔴 ZSet 底层是跳表 + 哈希表,所以"按分数排序"和"按 member 查分数"都是 O(logN),又快又顺。排行榜、延时队列、按时间线的 feed 都靠它。
七、五个场景对照表:选型不纠结
| 业务需求 | 用哪个 | 核心命令 |
|---|---|---|
| 商品库存 / 页面计数 / 验证码 | String | SET + DECR/INCR/SETEX |
| 分布式锁 | String | SETNX(或 SET NX EX) |
| 简单消息队列 / 最新列表 | List | LPUSH/RPOP + LTRIM |
| 用户对象 / 商品属性 | Hash | HSET/HGET/HINCRBY |
| 共同好友 / 标签去重 | Set | SINTER/SADD |
| 积分榜 / 热搜榜 / Top N | ZSet | ZADD/ZREVRANGE/ZINCRBY |
| 延迟队列 | ZSet | score 存时间戳,ZRANGEBYSCORE |
总结:你真正需要记住的 8 件事
- 五类型一句话选型:String 计数缓存、List 排队、Hash 对象、Set 去重交并差、ZSet 排序。
- String 单 value 上限 512MB;
INCR/DECR原子自增,并发计数/扣库存的关键,非整数会报错。 SETNX不存在才设,是分布式锁基础;SETEX设值+过期原子完成,别拆两步。- List 头尾 O(1)、中间 O(N),只从两端玩;
LRANGE 0 -1是全部(stop 含边界);LTRIM 0 99留最新 100 条。 - Hash 适合字段会独立改的对象;
HGETALL大对象慎用,用HSCAN。 - Set 的杀手锏是
SINTER/SUNION/SDIFF,共同好友一行搞定;元素无序不重复。 - ZSet 是排行榜本命:
member唯一、score可重复,重复加 member 会更新分数;ZREVRANGE出 Top N,ZRANK名次从 0 开始。 - 大集合别用
SMEMBERS/HGETALL一把梭(O(N) 阻塞),用对应的*SCAN增量遍历。
验证清单
- 能用
SETNX复现"第一个加锁成功、第二个失败" - 能说出
SETEX为什么比SET+EXPIRE更安全(原子性) - 能用 List 搭一个 LPUSH 生产 / RPOP 消费的小队列
- 会用
LTRIM 0 99把列表裁成"最新 100 条" - 能用 Hash 存一个用户对象,并单独
HINCRBY给 age 加 1 - 会用
SINTER算出两个人的共同关注 - 能用 ZSet 建一个积分榜,并用
ZREVRANGE ... WITHSCORES打出 Top3 - 会用
ZRANGEBYSCORE "(80" 100表达"分数严格大于 80" - 能说清为什么
INCR扣库存不会超卖、而"读-改-写"会
参考资源
- Redis 数据类型总览(官方)https://redis.io/docs/latest/develop/data-types/
- String 命令 https://redis.io/commands/#string
- List 命令 https://redis.io/commands/#list
- Hash 命令 https://redis.io/commands/#hash
- Set 命令 https://redis.io/commands/#set
- ZSet 命令 https://redis.io/commands/#sorted-set
下一篇《【上手】Python 连接 Redis》:用 redis-py 实战------连接池、五类型 CRUD、过期、pipeline 批量(附逐条 vs pipeline 实测耗时对比)、MULTI/WATCH 事务,以及一个能跑通的"查询先查缓存"最小示例,为后面的缓存实战篇打底。