面试知识库
进阶

Redis事务与Lua脚本#

一句话答案#

Redis 事务(MULTI/EXEC)不支持回滚,Lua 脚本保证原子性且支持逻辑判断,生产推荐 Lua。

核心要点

Redis 事务流程(MULTI/EXEC):

MULTI              # 开启事务,后续命令入队
SET key1 "a"       # QUEUED
SET key2 "b"       # QUEUED
INCR key3          # QUEUED
EXEC               # 一次性执行所有命令,返回每条结果

DISCARD            # 放弃事务(清空队列)
plaintext

WATCH 乐观锁:

WATCH stock:1001           # 监视 key
val = GET stock:1001       # 读取当前值
MULTI
DECRBY stock:1001 1        # 扣减库存
EXEC                       # 如果 stock:1001 在 WATCH 后被其他客户端修改 → EXEC 返回 nil(事务取消)
plaintext

事务 vs Lua 对比:

特性MULTI/EXEC 事务Lua 脚本
原子性❌ 不支持回滚,出错继续执行✅ 原子执行,中途不被打断
条件逻辑❌ 无法 if/else✅ 支持完整逻辑控制
乐观锁✅ WATCH 机制不需要(原子执行)
网络开销多次入队 + EXECEVAL 一次传输
错误处理命令错误不回滚已执行的脚本报错则整体不生效
生产推荐简单场景✅ 复杂原子操作首选

Lua 脚本使用:

# EVAL 直接执行(每次传输脚本全文)
EVAL "return redis.call('SET', KEYS[1], ARGV[1])" 1 mykey myvalue

# EVALSHA 按摘要执行(先 SCRIPT LOAD 缓存脚本,减少网络传输)
SCRIPT LOAD "return redis.call('SET', KEYS[1], ARGV[1])"
# 返回 SHA1: "a42059b356c875f0717db19a51f6aaa9161571a2"
EVALSHA "a42059..." 1 mykey myvalue
bash

典型 Lua 场景 — 库存扣减(先判断后扣减,原子执行):

local stock = tonumber(redis.call('GET', KEYS[1]))
if stock and stock >= tonumber(ARGV[1]) then
    redis.call('DECRBY', KEYS[1], ARGV[1])
    return 1  -- 扣减成功
else
    return 0  -- 库存不足
end
lua
面试回答(2分钟版)

Redis 提供两种保证操作原子性的方式:事务和 Lua 脚本。事务用 MULTI 开启、EXEC 提交,中间的命令会排队一次性执行,但它不支持回滚——如果某条命令执行出错,已执行的命令不会撤销,这和关系型数据库的事务有本质区别。事务还支持 WATCH 做乐观锁,如果被监视的 key 在 EXEC 前被其他客户端修改了,整个事务会取消。Lua 脚本是更推荐的方案,脚本在 Redis 中原子执行,执行期间不会被其他命令打断,而且支持 if/else 条件判断,可以实现更复杂的业务逻辑。典型的使用场景比如分布式锁的加锁解锁、库存扣减的先判断后扣减,都用 Lua 脚本保证原子性。生产中建议用 EVALSHA 代替 EVAL 减少网络传输,同时 Lua 脚本要尽量简短,执行时间过长会阻塞 Redis 主线程。在 Cluster 模式下操作多个 key 时需要用 Hash Tag 确保它们在同一个 slot。

追问与易错

追问方向:

  • “Redis 事务支持回滚吗?”→ 不支持回滚,EXEC 执行后如果某条命令出错(运行时错误),已执行的命令不会撤销;Redis 认为命令错误是编程 Bug 不应由数据库兜底,因此设计上不支持回滚
  • “Lua 脚本太长会怎样?”→ Lua 脚本在 Redis 中原子执行期间会阻塞主线程,脚本执行时间过长会导致其他客户端请求超时;生产上应保持脚本简短,默认 lua-time-limit=5000ms 超时后可用 SCRIPT KILL 终止
  • “EVAL 和 EVALSHA 区别?”→ EVAL 每次传输完整脚本文本,网络开销大;EVALSHA 先通过 SCRIPT LOAD 缓存脚本到 Redis 返回 SHA1 摘要,后续只传摘要执行,减少网络传输;生产推荐 EVALSHA

易错点:

  • ❌ Redis 事务和 DB 事务一样——不支持回滚
  • ❌ Lua 脚本可以很复杂——应尽量简短