玩转 Redis · String 基础篇
环境:Docker 容器
redis-learn(Redis 7.4.11,本机 127.0.0.1:6379) 本篇地位:五大类型之第一篇 + 全局通用指令(后续每篇都建立在它之上)
0. 一分钟回顾:Redis 是什么
- 仓库(硬盘):大而慢,取一趟要翻半天
- 便签(内存):小而快,伸手就拿到
Redis = 把所有数据都记在内存便签上的超快小本子程序。
一秒可处理十几万次读写;代价是断电可能丢数据(后续"持久化"篇章解决)。
为什么快(四层):
① 纯内存 ② 单线程处理命令 + 事件循环(会排队、不打架) ③ 高效数据结构 ④ 6.0 后网络读写也用了多线程,但命令执行仍是单线程(保住"天然原子"的优点)。
1. 练习环境备忘
docker exec -it redis-learn redis-cli --raw # 进入"聊天模式"(--raw 中文正常显示)
docker exec redis-learn redis-cli <命令> # 单发一条(写脚本用)
docker start redis-learn # 电脑重启后救活容器
PING→PONG:健康检查(确认网络通、Redis 活着)- 退出聊天模式:
exit或Ctrl+D - 在 Claude Code 对话里可用
!前缀直接执行命令
2. 必懂概念
key / value
一条便签 = key(条目名) + value(内容)。
key 与 value 的类型规则 ⭐
| 对象 | 规则 |
|---|---|
| key | 永远只有字符串一种。名字里写数字、中文都行,但统统按字符串对待。不存在"hash 型的 key" |
| value | 五大类型同时只能装一种形状(String/Hash/List/Set/ZSet 五选一) |
TYPE key | 查的其实是 value 的类型,不是 key 的 |
拿错形状的命令操作会报错(实测,Redis 7.4):
HSET demo:t2 name "小明" → 1
GET demo:t2 → WRONGTYPE Operation against a key holding the wrong kind of value
APPEND demo:t2 "x" → WRONGTYPE(同样报错)
例外特权:SET 无视原类型,直接覆盖(官方文档原话:"If key already holds a value, it is overwritten, regardless of its type"):
SET demo:t2 "hello" → OK ← hash 被直接替换成 string,不报错!
TYPE demo:t2 → string
⚠️ 版本差异警示:较老版本的 Redis 里 SET 也会报 WRONGTYPE(先删再建才能换形状)。行为跟着版本走,拿不准就当场实测 + 查官方文档。
key 命名法(重要约定)
业务:对象ID:字段 例:user:1:age、likes:post:1、sms:code
- 冒号对 Redis 没有特殊含义,本质还是一条普通 key,纯人类约定
- 好处:一眼看清归属;
KEYS 'user:2:*'通配符批量找;图形化工具显示成目录树 - key 名禁止放空格:放了就得每条命令伺候引号
GET "rename key" - key 名区分大小写:
name≠Name≠NAME(但命令不区分大小写,set=SET,大写只是习惯)
3. 通用指令(五种类型通吃 ⭐)
| 命令 | 作用 | 返回值 |
|---|---|---|
DEL k1 k2 … | 删除(可批量) | 真正删掉的条数(不存在的 0) |
EXISTS key | 在不在 | 1 在 / 0 不在 |
TTL key | 剩余寿命 | 正数=剩余秒 / -1 永不过期 / -2 不存在 |
EXPIRE key 秒 | 给已存在的 key 补定闹钟 | 1 成功 / 0 失败(不存在) |
PERSIST key | 撕掉闹钟,变永不过期 | 1 成功 / 0 失败 |
RENAME 旧 新 | key 改名(内容不动) | OK |
TYPE key | 查看类型(查的是 value) | string/hash/list/set/zset |
KEYS '模式' | 按通配符列出 key | key 列表 |
⚠️ KEYS 会翻遍全库,正式环境大数据量禁用,用温和版 SCAN(后续讲)。
⚠️ shell 里 * 会被展开,KEYS '*' 记得加引号。
4. String 专属指令
增
| 命令 | 说明 |
|---|---|
SET key value | 存一条(同名 = 覆盖任意类型,这也是最常用的"改") |
SET key value EX 秒 | 存的同时定闹钟 |
MSET k1 v1 k2 v2 … | 批量存 |
SETNX key value | 位置空才贴(NX = not exists)。1=贴上 / 0=已存在没动 → 分布式锁的种子 |
查
| 命令 | 说明 |
|---|---|
GET key | 取一条 |
MGET k1 k2 … | 批量取(一次跑腿带回多条) |
STRLEN key | 内容的字节数(一个汉字 = 3 字节,英文 1 字符 = 1 字节) |
改
| 命令 | 说明 |
|---|---|
同名 SET | 直接覆盖 |
APPEND key "内容" | 尾部续写,返回新长度(字节) |
GETSET key 新值 | 换新值,同时返回旧值 |
删
DEL(见通用指令)
计数器(数字专属"改")⭐
| 命令 | 说明 |
|---|---|
INCR key | +1;key 不存在自动当 0 加,新 key 直接 INCR 会变成 1 |
DECR key | −1 |
INCRBY key n / DECRBY key n | ±n |
为什么不用"GET→算→SET"? 两人同时点赞会丢数据(都拿到旧值 3,都写回 4)。INCR 把读改存打包成一步,Redis 内部排队处理,绝不插队 → 这个性质叫原子性。点赞数、阅读量、库存扣减都靠它。
对非数字内容 INCR → ERR value is not an integer or out of range(报错是保护)。
5. 返回值心法
- 大多数命令用 1/0 当灯泡开关(EXISTS/SETNX/EXPIRE/PERSIST…)
- DEL 返回数量,不是 0/1
- TTL 特例:
-1 永生,-2 不存在(正数留给秒数) - INCR 返回计算结果,不是标志
- 数字含义跟着命令走,不跟着数字走 → 忘了就
HELP 命令名当场查
6. 实操流水
6.1 过期闹钟实验(第 1 次实操)
127.0.0.1:6379> set tip "tips 5" EX 5
OK
127.0.0.1:6379> get tip
tips 5 ← 刚存完,还在
127.0.0.1:6379> get tip
←(等 5 秒后)空白 = 已自动消失(raw 模式 nil 不显示)
127.0.0.1:6379> TTL tip
-2 ← 已不存在
127.0.0.1:6379> get favorite-color
蓝色 ← --raw 模式下中文正常显示
6.2 SETNX 占坑实验
127.0.0.1:6379> SETNX use:1:name "xiaohong"
1 ← 贴上了(注:key 拼错 use,悄悄建了新 key,后已删)
127.0.0.1:6379> SETNX user:1:name "xiaohgon"
1 ← key 不存在 → 贴上
127.0.0.1:6379> SETNX user:1:name "xiaohgon"
0 ← key 已存在 → 拒绝
127.0.0.1:6379> GET user:1:name
xiaohgon ← 原值没被第二个 SETNX 动过
6.3 字节计数实验(STRLEN / APPEND)
127.0.0.1:6379> STRLEN user:1:name
8 ← xiaohgon:8 个英文字符 = 8 字节
127.0.0.1:6379> SET user:1:name "xiaohong"
OK
127.0.0.1:6379> APPEND user:1:name "gogogo"
14 ← 8 + 6 = 14 字节
127.0.0.1:6379> GET user:1:name
xiaohonggogogo
6.4 GETSET 换新取旧
127.0.0.1:6379> GETSET user:1:name "new name is xiaoming"
xiaohonggogogo ← 返回的是旧值!
127.0.0.1:6379> GET user:1:name
new name is xiaoming ← 新值已生效
6.5 RENAME 与"带空格的 key"翻车现场
127.0.0.1:6379> RENAME user:1:name "rename key"
OK ← 成功!新 key 的真名是「rename key」(中间带空格)
127.0.0.1:6379> GET user:1:name
← 空白:旧名已经不存在了
127.0.0.1:6379> GET rename key
ERR wrong number of arguments for 'get' command
← 空格把名字拆成两个参数
127.0.0.1:6379> GET "rename key"
new name is xiaoming ← 引号包住才行!
127.0.0.1:6379> RENAME "rename key" user:1:name
OK ← 改回正常名字
6.6 批量查 MGET
127.0.0.1:6379> MGET user:1:age user:2:address
18
chengdu ← 两条按顺序返回
6.7 大小写验证
127.0.0.1:6379> get user:1:name (小写命令)
new name is xiaoming ← 命令不区分大小写 ✅
127.0.0.1:6379> GET User:1:Name
(nil) ← key 区分大小写:这是另一条不存在的 key
EXISTS User:1:Name → 0 ← 铁证
6.8 类型规则验证(WRONGTYPE 与 SET 的特权)
HSET demo:t2 name "小明" → 1
TYPE demo:t2 → hash
GET demo:t2 → WRONGTYPE Operation against a key holding the wrong kind of value
APPEND demo:t2 "x" → WRONGTYPE
SET demo:t2 "hello" → OK ← SET 无视类型直接覆盖(官方文档确认)
TYPE demo:t2 → string
6.9 返回值全家福(对照速查)
EXISTS no-such-key → 0
SETNX demo:nx "v1" → 1
SETNX demo:nx "v2" → 0
DEL no-such-key → 0
EXPIRE no-such-key 10 → 0 ← key 不存在,闹钟定不上
TTL lesson:hello → -1 ← 永不过期
TTL no-such-key → -2 ← 不存在
INCR demo:cnt → 1 ← 新 key 从 0 起步
INCR demo:cnt → 2
DEL demo:nx demo:cnt → 2 ← 批量删,返回删掉的条数
6.10 连接诊断
redis-cli -p 9999 ping → Could not connect to Redis at 127.0.0.1:9999: Connection refused
redis-cli ping → PONG
7. 踩坑实录
| 坑 | 现象 | 教训 |
|---|---|---|
| 拼错 key 名 | GET favorit-color → (nil) | Redis 对不存在的 key 不报错;用 ↑ 翻历史改,别重打 |
| key 里带空格 | GET rename key → ERR 参数数量 | key 名禁空格;有的话必须 GET "rename key" |
| raw 模式取不存在的 key | 输出空白一行 | 空白 = nil = 不存在;要明确判断用 EXISTS / TTL(-2) |
| GET 多带了参数 | GET k "v" → ERR | GET 只收一个 key,粘贴时注意 |
| 多打了反斜杠 | Invalid argument(s) | 命令行里多出的字符会被当参数 |
| 两个终端同时操作 | 前后查询结果"矛盾" | Redis 是共享数据库,多客户端并发读写;判断状态要当场验证 |
| Tab 补全没反应 | — | redis-cli 的 Tab 只补命令名(Z+Tab),不补 key 名 |
| 记忆与版本不符 | 以为 SET 会报 WRONGTYPE,实际 OK 覆盖 | 行为跟着版本走:当场实测 + 查官方文档,别全信记忆 |
8. 自检清单(都能答上 = 本篇毕业 ✅)
- Redis 为什么快?断电为什么可能丢数据?
-
SET fruit apple谁是 key 谁是 value? - TTL 返回 5 / -1 / -2 分别是什么意思?
- SETNX 什么时候返回 0?
- "小明"的 STRLEN 是几?为什么?
- 点赞为什么用 INCR 而不是 GET+SET?
-
user:1:age这种命名好在哪?冒号对 Redis 有意义吗? - 命令大小写敏感吗?key 呢?
- key 可以是什么类型?value 呢?
- 拿 GET 去读一个 hash 类型的 key 会怎样?SET 呢?
9. 全图:五大类型地图(本篇已完成 1/5)
| 篇章 | 形状 | 典型用途 | 招牌命令 | 状态 |
|---|---|---|---|---|
| 📗 String | 一行字的纸条 | 缓存、计数器 | SET GET INCR | ✅ |
| 📘 Hash | 小表格(字段↔值) | 对象、单字段修改 | HSET HGET | ⬅️ 下一站 |
| 📙 List | 两头进出的队伍 | 消息队列、最新列表 | LPUSH RPOP | ⏳ |
| 📕 Set | 不重复的一筐球 | 去重、标签、共同好友 | SADD SINTER | ⏳ |
| 📓 ZSet | 带分数自动排队 | 排行榜、优先级 | ZADD ZRANGE | ⏳ |
记忆锚点:专属指令首字母 = 类型名(H→Hash,L→List,S→Set,Z→ZSet)。