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 缓存穿透流程示意图

下图展示了缓存穿透的完整请求链路:

缓存穿透请求链路 客户端 缓存层(Redis) 数据库(MySQL) ① 查询 Key ② 未命中,穿透 ③ 查询空结果 返回空结果(不写缓存) ④ 每次请求都穿透,缓存始终不命中 穿透后果说明 • 缓存层:命中率趋近于 0,所有请求绕过缓存 • 数据库层:承受全部请求压力,连接池、CPU、IO 迅速耗尽 • 一致性:缓存空洞持续存在,一致性维护机制失效

1.6 穿透与正常请求对比

为了更直观地理解缓存穿透的危害,下表对比了正常缓存请求与穿透请求的差异:

对比维度 正常缓存请求 缓存穿透请求
请求路径 客户端 → 缓存(命中)→ 返回数据 客户端 → 缓存(未命中)→ 数据库(空结果)→ 返回空
数据库压力 仅首次或缓存过期时查询 每次请求都查询数据库
缓存命中率 高(通常 > 90%) 低(趋近于 0%)
响应时间 毫秒级(缓存读取) 百毫秒级(数据库查询 + 网络开销)
系统稳定性 稳定 极易引发雪崩
缓存一致性 可通过常规策略维护 一致性维护链路断裂