908 字
约 3 分钟
8
S12-J04 · 事务、Lua 与 Pipeline
适用读者:以为
MULTI能回滚、或把 Pipeline 当事务的人。
前置:S12-B01。
实测环境:Redis 7.0.15(2026-11-05)。
覆盖旧文:876(86) 1572(77) 1570(35) = 198 点。
覆盖扩展:E-S12-013(三者边界对比)。
上一篇:S12-J03 · 分布式锁:SETNX → Redisson → RedLock
下一篇:S12-J05 · 高级用法。
1. 一句话结论
三者保证的都是"不被打断",不是"可回滚"。MULTI/EXEC 出错不撤销、Lua 出错也不撤销——Redis 从来没有回滚。
实测(同一个错误,两种方式):
① MULTI ... SET k_a 1 / INCR s1(字符串) / SET k_b 2 ... EXEC
→ k_a=1 k_b=2 ← 两条 SET 仍生效,只有 INCR 失败
② EVAL "set k_c 1; incr s1" ...
→ ERR value is not an integer 且 k_c=1 ← set 已生效
2. 三者的定位
| 机制 | 原子性 | 回滚 | 用途 |
|---|---|---|---|
| MULTI/EXEC | 连续执行不被打断 | ❌ | 多条命令成组 |
| Lua 脚本 | 整个脚本不被其他命令插入 | ❌ | 读-改-写(如锁校验、限流) |
| Pipeline | ❌(只是批量发送) | ❌ | 减少 RTT,不是原子 |
判据:
要"读一个值再根据它写" → Lua(真正的读-改-写原子)
只是"多条独立命令一起发" → Pipeline(快)
要"要么全成功" → ❌ Redis 做不到(用数据库事务)
3. 为什么 Pipeline ≠ 事务
| 维度 | Pipeline | MULTI/EXEC |
|---|---|---|
| 网络往返 | 1 次 | 1 次(EXEC 时) |
| 原子性 | ❌ 中间可插入其他客户端命令 | ✅ |
| 错误处理 | 逐条看结果 | 逐条看结果 |
"Pipeline 更快是因为少 RTT",不是因为原子——这点常被误解。
4. Lua 的正确用法与限制
| 要点 | 说明 |
|---|---|
KEYS[] / ARGV[] |
传参规范(不要拼接字符串) |
| 脚本要短 | 长脚本会阻塞全局(B01 实测 1441 ms) |
| 只做纯计算+少量命令 | 不要在 Lua 里循环跑几万次 |
SCRIPT LOAD + EVALSHA |
减少传输 |
实测(原子计数):
redis-cli eval "return redis.call('incrby', KEYS[1], 5)" 1 cnt
# → 5
5. WATCH:乐观锁的替代
WATCH key → 读 → MULTI → 写 → EXEC
EXEC 返回 nil 表示 key 被别人改过 → 重试
| 特点 | 说明 |
|---|---|
| 乐观 | 不加锁,冲突才失败 |
| 需重试 | 高冲突下性能差 |
| 已被 Lua 取代 | Lua 更简单直接 |
6. 五个误区
| 误区 | 真相 |
|---|---|
| "MULTI 能回滚" | 实测不回滚 |
| "Lua 出错会撤销" | 已执行的命令保留(实测 k_c=1) |
| "Pipeline 是事务" | 只是批量发送 |
| "Lua 越长越好" | 长脚本阻塞全局 |
"WATCH 没有代价" |
冲突要重试 |
7. 自检
- 三者各保证什么、不保证什么?
- 实测里
MULTI内INCR失败,两条 SET 生效了吗? - Lua 出错后已执行的命令会撤销吗?
- Pipeline 为什么快?
- 什么时候该用
WATCH?
8. 下一篇
S12-J05 · 高级用法:延迟队列 / 订阅 / 限流 / SCAN
事务讲完,下一篇把几个高频用法落地,并解释为什么生产禁用 KEYS(已有三组实测)。
本篇属于系列《S12 · Redis 与缓存》。旧文来源:A-876/1572/1570;扩展点 E-S12-013。事务与 Lua 错误语义为 Redis 7.0.15 实测。
S12-J04 · 事务、Lua 与 Pipeline
https://www.clxhxhhr.top/posts/5353/ 评论
0 条
还没有评论,先写一条吧。