前言
我们使用缓存主要是为了提高查询效率,将一些数据库慢查询或频繁的查询结果缓存起来,下次就可以不用查数据库,直接从缓存取。由于缓存使用内存存储,加上一些特殊的数据结构,所以存取速度比数据库快几个等级。然而使用缓存也有很多需要注意的点,一不小心可能就会踩坑,导致大量查询落到数据库,严重的话会把数据库压垮,导致服务不可用,影响系统性能和稳定。接下来我将介绍下缓存的三大经典问题:穿透、雪崩和击穿。
缓存穿透
数据既不在缓存中,也不在数据库中。

常见场景:攻击者伪造了大量的请求,请求某个不存在的数据。
- 缓存里面没有对应的数据,所以查询会落到数据库上。
- 数据库也没有数据,所以没有办法回写缓存,下一次请求同样的数据,请求还是会落到数据库上。
解决方案:
思路是数据不存在,要防止每次都去查数据库
- 回写特殊值,缓存一个固定值代表空,缺点是:大量 null 值会把redis 内存耗尽
- 布隆过滤器
- 业务上判断:业务上不能允许用户使用不存在的数据作为缓存key
缓存雪崩
缓存里大量 key 在同一时刻过期,导致请求都落到了数据库上。

常见场景:项目上线前做缓存预热,将大量的 key 设置为同一超时时间,导致到期后,大量请求落到数据库。
解决方案:缓存预热的时候,设置固定的时间+随机数,比如在 15 分钟的基础上加上一个 0~180 秒的偏移量,偏移量要跟过期时间成正比,不能过低或者过高。
缓存击穿
热 key 失效,导致这个热 key 的大量请求落到数据库。如果请求的数据并不是什么热点数据,那么击穿也没有什么问题,它就是普通的缓存未命中而已。

常见场景:
- 首页推荐列表、实时榜单(如热搜榜),缓存过期时会引发集中查询
- 电商平台的爆款商品,每秒有成百上千次访问,若缓存过期,所有请求会同时访问数据库
- 秒杀活动、节日促销页面,大量用户同时刷新,缓存过期瞬间数据库被压垮
解决方案:
- singleflight 模式:使用 singleflight 模式合并并发请求,同一时间对同一 key 的多个请求,只让一个请求去查询数据库,其他请求等待并复用结果(适合 Go 等支持协程的语言)
- 热 Key 永不过期:通过后台任务定期更新缓存(如每小时更新一次),或在数据变更时主动更新缓存
- 互斥锁(分布式锁):缓存过期时,只有第一个请求能获取到锁并执行数据库查询 + 缓存重建,其他请求获取锁失败时,等待一段时间后重试(从缓存获取新值)
不建议使用缓存
- 查询频率不高
- 更新频繁,缓存命中率低
- 数据一致性要求极高