1. 缓存穿透问题概述
1.1 定义
缓存穿透(Cache Penetration)是指查询一个一定不存在的数据,由于缓存层无法命中(缓存中没有该数据的副本),每次请求都直接穿透到后端数据库层执行查询。由于数据库中也不存在该记录,每次查询都返回空结果,且不会回写缓存,导致后续相同请求依然穿透到数据库。
核心特征:缓存层形同虚设,所有请求直达数据库,且数据库返回空结果后不写入缓存。
1.2 产生原因
缓存穿透主要由以下两类场景引发:
| 原因分类 | 具体描述 | 典型示例 |
|---|---|---|
| 恶意攻击 | 攻击者利用系统业务漏洞,批量构造大量不存在的 Key 发起高频请求,绕过缓存直接压垮数据库。 | 用户 ID 采用自增主键,攻击者遍历 -1、-2、0 或超大 ID 发起查询 |
| 空数据查询 | 业务逻辑中,用户查询了数据库中确实不存在的数据(如已删除的记录、未生成的订单号),且系统未对空结果做缓存处理。 | 查询已下架的商品详情、查询不存在的快递单号 |
| 缓存与数据库数据不一致 | 缓存中数据因过期或淘汰被清除,但数据库对应记录已被物理删除,导致后续查询持续穿透。 | 缓存 TTL 到期后,数据库记录已被清理,查询始终为空 |
注意:空数据查询在正常业务中也会偶发出现,但恶意攻击会将这种偶发放大为持续的高并发穿透,危害远大于正常业务场景。
1.3 对后端数据库的影响
缓存穿透对数据库层造成的冲击是直接且严重的,具体表现为:
- 连接池耗尽:大量请求同时穿透到数据库,数据库连接池瞬间被占满,后续正常请求无法获取连接。
- CPU 与 IO 飙升:数据库需要为每个穿透请求执行完整的 SQL 解析、索引扫描、磁盘 IO 操作,CPU 和 IO 负载急剧上升。
- 慢查询堆积:对于不存在的数据,数据库仍需扫描索引或全表来确认记录不存在,产生大量无意义的慢查询。
- 级联故障风险:数据库响应变慢后,应用层线程阻塞,请求队列积压,最终可能导致整个服务雪崩。
经验数据:在未加防护的系统中,1000 QPS 的穿透请求可在 10 秒内将数据库 CPU 从 20% 推至 100%,导致数据库连接超时。
1.4 对缓存一致性的影响
缓存穿透不仅影响数据库,还会破坏缓存一致性治理的根基:
| 影响维度 | 具体表现 | 后果 |
|---|---|---|
| 缓存空洞 | 不存在的数据永远不会被写入缓存,缓存层出现大量逻辑空洞。 | 缓存命中率持续下降,穿透率上升,形成恶性循环。 |
| 缓存污染 | 若错误地将空结果(如 null、空字符串)写入缓存,会占用缓存空间,且无法区分“数据不存在”与“缓存未命中”。 | 缓存空间浪费,且后续数据真正写入时,缓存中仍保留空值,导致数据不一致。 |
| 一致性窗口扩大 | 穿透请求绕过缓存直接查库,绕过了缓存一致性维护机制(如延迟双删、Binlog 监听),导致缓存与数据库的同步链路断裂。 | 即使后续数据被写入,缓存可能仍处于过期或缺失状态,一致性无法保证。 |
| 缓存雪崩诱因 | 大量穿透请求导致缓存层被旁路,缓存失效后新请求继续穿透,最终引发缓存雪崩。 | 系统整体可用性下降,甚至宕机。 |
1.5 缓存穿透流程示意图
下图展示了缓存穿透的完整请求链路:
1.6 穿透与正常请求对比
为了更直观地理解缓存穿透的危害,下表对比了正常缓存请求与穿透请求的差异:
| 对比维度 | 正常缓存请求 | 缓存穿透请求 |
|---|---|---|
| 请求路径 | 客户端 → 缓存(命中)→ 返回数据 | 客户端 → 缓存(未命中)→ 数据库(空结果)→ 返回空 |
| 数据库压力 | 仅首次或缓存过期时查询 | 每次请求都查询数据库 |
| 缓存命中率 | 高(通常 > 90%) | 低(趋近于 0%) |
| 响应时间 | 毫秒级(缓存读取) | 百毫秒级(数据库查询 + 网络开销) |
| 系统稳定性 | 稳定 | 极易引发雪崩 |
| 缓存一致性 | 可通过常规策略维护 | 一致性维护链路断裂 |