redis查不到数据时err≠nil不一定是真错误,可能是redis.nil这一正常空响应;需用errors.is(err, redis.nil)判断,而非==;应写特殊标记值(如"__null__")加短ttl缓存空值,并用setnx避免并发写入冲突。

Go 里用 Redis 查不到数据时,err 不等于 nil 就代表真出错了?
不是。Redis 客户端(比如 github.com/go-redis/redis/v9)把「键不存在」也包装成一个错误,即 redis.Nil。它属于 err != nil,但不是网络或序列化故障,而是业务上正常的空响应。
-
redis.Nil是一个预定义的错误变量,专门用来标识GET、HGET等命令没查到值 - 直接判
err != nil并返回错误,会导致缓存穿透:前端反复查一个根本不存在的 ID,每次请求都打到后端 DB - 必须显式比较:
errors.Is(err, redis.Nil),而不是用==(因为底层是不同实例) - 注意 v8 和 v9 的写法差异:v8 用
err == redis.Nil,v9 必须用errors.Is
查 Redis 返回 redis.Nil,该不该写空值回缓存?
该,但得加短 TTL,且不能无条件写。
- 直接
SET key "" EX 60会埋下隐患:如果业务逻辑里空字符串是合法值(比如用户昵称允许为空),那就和「查无此键」混淆了 - 更稳妥的做法是写一个特殊标记值,比如
"__null__"或 JSON{"_null":true},读取时先判断是否为该标记 - TTL 别设太长(建议 1–5 分钟),否则异常数据会长期阻塞真实数据写入
- 别在事务或 pipeline 里盲目补空值——万一上游刚删了键、你立刻补,就造成数据不一致
Go 用 redis.Client.Get() 解析失败,err 是什么类型?
常见两类:协议级错误(如连接断开、超时)和类型转换错误(如存的是字符串,却用 Scan 解成 struct)。
-
Get().Result()返回(string, error);如果 Redis 里存的是非字符串(比如用HMSET存的哈希),调Result()会报redis: can't unmarshal类错误 - 想安全取结构体,别用
Get+ 手动json.Unmarshal,改用Get后检查err是否为nil或redis.Nil,再解码;解码失败要单独处理,别和 Redis 连接错误混在一起 return - 用
Val()拿原始字节再手动解,比直接Result()多一层控制,适合需要区分「空值」和「解码失败」的场景
为什么加了空值缓存,线上还是有大量 DB 查询?
大概率是缓存写入本身失败了,或者并发请求没共享空值判断逻辑。
立即学习“go语言免费学习笔记(深入)”;
- 写空值那步没做错误检查:
client.Set(ctx, key, "__null__", 60).Err()如果返回非nil(比如 Redis 写满、OOM),空值根本没落库,后续请求照旧穿透 - 多个 goroutine 同时发现缓存 miss,都去查 DB,然后都尝试写空值——但只有第一个成功,其余写失败,却没 fallback 到“等别人写完再读一次”
- 更稳的做法:用
SET key __null__ EX 60 NX(即SetNX),只让一个请求写成功;其他请求可以短暂 sleep 后重试 Get,或直接走降级逻辑 - 别忽略 context 超时:DB 查询用了 2s,但 Redis 写空值的 context 只剩 100ms,导致写入被 cancel,空值丢失
空值缓存不是开关一开就万事大吉,它本身就有竞态、失败、过期策略这些细节要对齐业务节奏。最容易被跳过的,是写空值那一步的错误处理和重试兜底。










