跳转到内容

事务与 Lua 脚本

用途:需要“多命令原子执行”或“读-改-写”并发安全时查阅

MULTI
INCR counter
LPUSH list v
EXEC

特点与限制:

特性 说明
入队而非立即执行 EXEC 前不产生效果
无回滚 某条命令执行失败(运行时错误),其余仍会执行;只有入队阶段的语法错误会让整个事务不执行
无法基于读结果分支 事务内不能“读到值再决定做什么”
WATCH 提供乐观并发 被监视 key 在 EXEC 前被改则事务放弃(返回 nil),需客户端重试

结论:MULTI 适合“一批独立命令一起发送”(减少 RTT),不适合需要条件判断与原子读改写的场景。

脚本在服务端原子执行(执行期间不插入其他命令),可读可写、可分支、可返回复合结果。

-- KEYS[1] 计数器,ARGV[1] 阈值,ARGV[2] 过期秒数
local cur = tonumber(redis.call('GET', KEYS[1]) or '0')
if cur + 1 > tonumber(ARGV[1]) then return -1 end
local n = redis.call('INCR', KEYS[1])
if n == 1 then redis.call('EXPIRE', KEYS[1], ARGV[2]) end
return n

适用场景:限流计数、分布式锁的校验解锁、库存扣减、复杂条件更新、批量操作合并。

  1. 阻塞风险:脚本执行期间实例不处理其他请求,任何循环或全量扫描都会拖垮实例。必须保证 O(小常数) 复杂度。
  2. Cluster 下所有 KEYS 必须同 slot,用 hash tag 保证。
  3. 不要依赖随机/时间:脚本需可重放(redis.call('TIME')、math.random 在旧版本会破坏复制语义;新版本对只读脚本有放松,但仍应避免)。
  4. 写操作必须通过 redis.call 显式声明 key,否则在 Cluster/AOF 复制下行为不正确。
  5. 脚本缓存:EVALSHA + SCRIPT LOAD 减少传输;SCRIPT KILL 只能杀未写过的脚本,写过就得 SHUTDOWN NOSAVE——所以务必先在测试环境验证。
  6. 用 EVAL_RO/函数库(Redis Functions)等较新机制时确认版本支持。

Redis 原子操作适合“以 Redis 为权威源”的计数、限流、锁。业务数据的正确性(余额、库存)应以数据库条件更新为最终权威(UPDATE ... SET x = x - 1 WHERE x >= 1),Redis 只做加速与预检,否则故障恢复时会出现无法解释的偏差。

误区 修正
认为 MULTI 有回滚 无回滚,需要原子判断与写入就用 Lua
脚本里遍历大集合 阻塞实例,改为分批或客户端处理
Cluster 下跨 slot 脚本 用 hash tag 或拆脚本
用 Redis 计数当业务库存权威 以 DB 条件更新为权威,Redis 做预检
忘设过期导致计数永久累积 计数 key 必须有 TTL(如按时间窗口分 key)
  • Redis 官方文档:Transactions、Lua scripts、Patterns(限流示例)
  • 《Redis 设计与实现》脚本与事务章节