GoRedis 不支持原生跨命令原子事务,需用Lua脚本(保证全成功或全失败)或Watch+TxPipeline(乐观锁,需手动重试)模拟;强一致性场景须结合GORM或业务层协调。

GoRedis 本身不支持跨命令的原子事务(不像 Redis 的 MULTI/EXEC 那样保证多个命令要么全执行、要么全不执行),但可以通过 redis.TxPipeline 或 Lua 脚本模拟事务语义;真正需要强一致性的场景,必须结合 GORM 事务或业务层协调,不能只靠 GoRedis。
GoRedis 没有传统意义上的事务,只有 pipeline 和 Lua
很多人误以为 redis.TxPipeline 是“事务”,其实它只是把多个命令打包发送、减少 RTT,并不提供回滚能力。如果中间某个命令失败(比如 SET 时 key 被删了、INCR 时类型错误),后续命令仍会执行,且错误不会中断 pipeline。
-
redis.TxPipeline只是批量发送,不是 ACID 事务 - 真正要原子性,得用
eval执行 Lua 脚本,Redis 保证脚本内操作全成功或全失败 - GoRedis 的
Watch+TxPileline组合可实现 CAS 类似效果,但需手动处理失败重试
用 Lua 脚本保证 Redis 操作原子性
这是最可靠的方式。例如:用户扣余额 + 记录日志,两个操作必须一起成功或一起失败。
示例脚本(存为 deduct_and_log.lua):
if redis.call("GET", KEYS[1]) >= ARGV[1] then
redis.call("DECRBY", KEYS[1], ARGV[1])
redis.call("RPUSH", KEYS[2], ARGV[2])
return 1
else
return 0
end
Go 中调用:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
script := redis.NewScript(luaContent)
result, err := script.Run(ctx, rdb, []string{"user:1001:balance", "log:user:1001"}, "50", "deduct_50_at_20260811").Result()
if err != nil {
// 处理网络或语法错误
}
if result == int64(0) {
// 余额不足,业务上视为失败
}
- Lua 脚本在 Redis 单线程中执行,天然原子
- 脚本里不能调用阻塞命令(如
SLEEP),也不能访问外部状态 - 注意传参用
KEYS和ARGV,避免硬编码导致脚本不可复用
Watch + TxPipeline 实现乐观锁式“事务”
适用于读-改-写场景,比如计数器自增但不超过上限。
关键点:
- 先
Watch目标 key,再在TxPipeline中构造操作 - 提交前检查 key 是否被其他客户端修改过(通过
Exec返回nil判断失败) - 失败后需重试,业务代码要处理循环或超时
示例:
for i := 0; i < 3; i++ {
pipe := rdb.TxPipeline()
pipe.Watch(ctx, "counter")
val, _ := rdb.Get(ctx, "counter").Int64()
if val < 100 {
pipe.Incr(ctx, "counter")
}
_, err := pipe.Exec(ctx)
if err == nil {
break // 成功
}
if err == redis.Nil { // 被其他 client 修改,重试
continue
}
return err
}
Gin 中整合时要注意连接复用和上下文传递
在 Gin handler 里用 GoRedis,别每次请求都新建 client;同时注意 ctx 生命周期。
- 全局复用
*redis.Client,不要在 handler 里redis.NewClient - 传入 handler 的
context.Context要带 timeout,避免 Redis 阻塞整个 HTTP 请求 - 别把
ctx.Background()硬编码进 Redis 调用,应从 Gin 的c.Request.Context()衍生 - 错误处理要区分网络错误(
redis.Timeout)、业务错误(Lua 返回值)、协议错误(redis.Nil)
实际最容易被忽略的是:Lua 脚本调试困难,出错只返回 generic error,建议上线前用 redis-cli --eval 先验证逻辑;另外 Watch 机制在高并发下重试率高,不如直接用 Lua 更可控。

















