From 9cb53bad9c9220654ba65ff90d69d46d75233b70 Mon Sep 17 00:00:00 2001
From: hhs <386998068@qq.com>
Date: Thu, 21 May 2026 23:55:49 +0800
Subject: [PATCH] vault backup: 2026-05-21 23:55:49
---
hhs/Redis/03-基本命令速查.md | 101 +++++++++++++++++++++++++++++------
1 file changed, 85 insertions(+), 16 deletions(-)
diff --git a/hhs/Redis/03-基本命令速查.md b/hhs/Redis/03-基本命令速查.md
index 8e43de9..2c4db29 100644
--- a/hhs/Redis/03-基本命令速查.md
+++ b/hhs/Redis/03-基本命令速查.md
@@ -252,16 +252,36 @@ for iter.Next(ctx) {
## 管道 Pipeline vs 事务 Transaction
-Redis 协议本身就是 request/response,Pipeline 把多个命令打包一次发送,大幅减少网络往返;而事务通过 `MULTI` / `EXEC` 保证一组命令的**原子执行**。
+### 先理解问题:为什么要"打包"命令?
+
+Redis 通信遵循**一问一答**(request/response)模式——客户端发一条命令,等回复,再发下一条。每次"发送 → 等待 → 接收"的网络往返叫做一次 **RTT**(Round-Trip Time,往返时延)。
+
+> [!QUESTION] RTT 有多贵?
+> 假设内网 RTT = 0.5ms,发送 1000 条命令逐条执行需要 500ms。如果把它们**打包一次发送**,只需要 ~0.5ms——**性能差 1000 倍**。
+
+Pipeline 和 Transaction 都是"打包"方案,但它们解决的问题不同:
+
+```mermaid
+flowchart LR
+ A["客户端发送 N 条命令"] --> B{"核心诉求?"}
+ B -- "快!" --> C["Pipeline
减少网络往返"]
+ B -- "安全!" --> D["Transaction
保证命令原子执行"]
+```
### Pipeline —— 批量发送,减少 RTT
+Pipeline 的原理很简单:客户端先把 N 条命令存在本地缓冲区,**一次性**发送给 Redis 服务端,服务端依次执行后把 N 个回复**一次性**返回。
+
+> [!NOTE] Pipeline 的两个关键点
+> 1. **不保证原子性**——Pipeline 中的命令仍然可能被其他客户端的命令穿插执行
+> 2. **不关心业务逻辑**——它纯粹是一个"网络优化层",减少的是等待时间
+
```go
pipe := rdb.Pipeline()
pipe.Set(ctx, "k1", "v1", 0)
pipe.Set(ctx, "k2", "v2", 0)
pipe.Set(ctx, "k3", "v3", 0)
-results, err := pipe.Exec(ctx) // 一次性发送,一次性接收
+results, err := pipe.Exec(ctx) // 一次性发送,一次性接收所有结果
```
```bash
@@ -270,37 +290,86 @@ redis-cli --pipeline <<< $'SET k1 v1\nSET k2 v2\nSET k3 v3'
```
> [!TIP] Pipeline 不是银弹
-> 单次 batch 控制在 50~200 条为佳。过大反而会增加 RTT 等待时间,且占用服务端协程。
+> 单次 batch 控制在 50~200 条为佳。过大反而会增加 Redis 服务端的内存压力和等待时间。
### MULTI / EXEC —— 原子化执行
+事务解决的是另一个问题:**我想让一组命令"一起执行",中间不能被别人插队**。
+
+`MULTI` → `命令1` → `命令2` → `EXEC` 的执行流程是:
+
+```mermaid
+sequenceDiagram
+ participant C as "客户端"
+ participant S as "Redis 服务端"
+ C->>S: MULTI(开启事务)
+ S-->>C: OK
+ C->>S: SET acc:A 900
+ S-->>C: QUEUED(排队,不立即执行)
+ C->>S: SET acc:B 1100
+ S-->>C: QUEUED
+ C->>S: EXEC(提交)
+ S-->>C: [OK, OK](依次执行,一次性返回结果)
+```
+
+> [!QUESTION] "QUEUED" 是什么意思?
+> `MULTI` 之后、`EXEC` 之前的命令**不会立即执行**,而是进入服务端的队列。只有收到 `EXEC` 后,Redis 才会**依次**执行队列中的所有命令,并把结果一次性返回。
+
```go
-pipe, cancel := rdb.TxPipeline()
+pipe, cancel := rdb.TxPipeline() // TxPipeline = Transaction Pipeline,即 MULTI/EXEC 包裹的 Pipeline
defer cancel()
pipe.Set(ctx, "acc:A", "900", 0)
pipe.Set(ctx, "acc:B", "1100", 0)
-_, err := pipe.Exec(ctx) // 全部命令依次执行(不被其他命令打断)
+_, err := pipe.Exec(ctx) // 触发 EXEC,所有命令原子执行
```
```bash
# Redis CLI 中的事务
MULTI
-> SET acc:A 900
-> SET acc:B 1100
-EXEC # 原子提交
+> SET acc:A 900 # QUEUED
+> SET acc:B 1100 # QUEUED
+EXEC # 原子提交
```
-> [!NOTE] Pipeline vs Transaction 对比
-> | 维度 | Pipeline | Transaction (MULTI/EXEC) |
-> |------|----------|------------------------|
-> | **目的** | 减少网络往返(性能) | 保证原子性(一致性) |
-> | **命令失败处理** | 单个失败不影响其他命令 | `EXEC` 前语法错误会取消整个事务 |
-> | **WATCH 支持** | 不支持 | 支持乐观锁 (`WATCH key`) |
-> | **适用场景** | 批量写入、批量查询 | 转账、库存扣减等需要原子性的操作 |
+### WATCH —— 乐观锁(防止"并发踩踏")
+
+事务保证了"不插队",但如果另一个客户端在我 `WATCH` 到 `EXEC` 之间修改了数据怎么办?`WATCH` 就是为此设计的——它在 `EXEC` 前检查被监视的 key 是否被改过,**如果改了就自动放弃整个事务**。
+
+```mermaid
+sequenceDiagram
+ participant A as "客户端 A"
+ participant S as "Redis 服务端"
+ participant B as "客户端 B"
+ A->>S: WATCH stock:1001
+ A->>S: MULTI
+ A->>S: DECR stock:1001
+ B->>S: SET stock:1001 999(被其他客户端修改)
+ A->>S: EXEC
+ S-->>A: nil(事务被取消,因为 stock:1001 已变)
+```
+
+> [!NOTE] WATCH 的使用范式
+> 1. `WATCH key` —— 监视目标 key
+> 2. 读取 key 的值,在客户端做判断
+> 3. `MULTI` → 写命令 → `EXEC`
+> 4. 如果 `EXEC` 返回 `nil`,说明数据被改过,需要**重试整个流程**
+>
+> 这就是经典的 **CAS(Compare-And-Swap)** 模式。实际开发中,Lua 脚本通常更简洁可靠。
+
+### 对比总结
+
+| 维度 | Pipeline | Transaction (MULTI/EXEC) |
+|------|----------|------------------------|
+| **核心目的** | 减少网络往返(**性能优化**) | 保证命令连续执行(**一致性**) |
+| **原子性** | ❌ 不保证 | ✅ 保证(不会被其他命令插队) |
+| **命令失败处理** | 单个失败不影响其他命令 | 语法错误 → 整个事务取消;运行时错误 → 仅该命令失败,其他继续 |
+| **WATCH 支持** | ❌ | ✅ 乐观锁 |
+| **适用场景** | 批量写入、批量查询 | 转账、库存扣减等需要原子性的操作 |
+| **性能开销** | 几乎无额外开销 | 略高于 Pipeline(需要服务端队列) |
> [!WARNING] Redis 事务不是"数据库事务"
-> Redis 事务只保证**不中断**(没有回滚),中间执行的命令如果出错,后续命令仍会继续。如果需要"出错就回滚"的行为,请使用 Lua 脚本。
+> 传统数据库的事务有 **ACID** 保证,失败会回滚。Redis 事务只保证**不中断**(队列中的命令会全部执行),中间命令出错**不会回滚**。如果需要"出错就回滚",请使用 Lua 脚本。
## Pub/Sub — 发布订阅