标准LRU在SQL数据库缓存中易失效,因其“刚访问即热”假设不成立:全表扫描、临时JOIN等一次性查询会挤占热点数据,且LRU无法区分同页内不同深度的访问模式。

为什么标准LRU在SQL数据库缓存中容易失效
标准LRU(最近最少使用)假设“刚被访问过的数据,近期还会被访问”,但SQL查询场景下这个假设常不成立。比如全表扫描会把大量冷数据推入缓存并挤走热点行;又如临时JOIN中间结果、一次性报表查询,它们只用一次却长期占据缓存空间。更关键的是,数据库缓存单位通常是数据页(Page)或块(Block),而非单行,而不同查询对同一页面的访问模式差异大——有的只读首几行,有的遍历全部,LRU无法区分这种“访问深度”差异。
基于访问频率与时间双维度的LFU-LRU混合策略
单纯看频率(LFU)易受短期突发查询干扰,单纯看时间(LRU)忽略长期热度。实用做法是给每个缓存项维护两个计数器:访问频次(counter)和最后访问时间戳(last_access)。淘汰时优先淘汰(counter × α + (now − last_access) × β)值最大的项,其中α、β为可调权重(例如α=1, β=0.01)。这样既保留高频热数据,又及时清理长时间未触达的低频页。
- 实现上可在缓存条目结构中增加两个字段,每次get/set时更新
- 为避免counter无限增长,可采用“衰减式LFU”:每隔固定周期(如5分钟)将所有counter右移1位(等效乘以0.5)
- PostgreSQL的
shared_buffers虽未直接暴露该算法,但其clock-sweep机制已隐含类似思想
面向查询语义的访问权重分级
不是所有访问都应被同等对待。SELECT主键查询、索引范围扫描、聚合GROUP BY、全表扫描,对缓存价值的贡献差异显著。可在查询解析阶段打标,赋予不同权重:
- 主键等值查询 → 权重3(高价值,大概率复用)
- 索引范围扫描 → 权重2(中价值,局部复用可能)
- 无索引WHERE或ORDER BY + LIMIT → 权重1(低价值,易导致随机IO)
- 全表扫描/临时表构建 → 权重0(不计入热度,或触发强制短生命周期)
缓存项的实际热度 = 原始counter × 查询权重。这要求缓存层与查询优化器轻耦合,MySQL 8.0+可通过Query Rewrite插件+自定义UDF初步支持。
分层缓存+冷热分离的物理隔离设计
把一块内存硬切成“热区”和“冷区”比在统一空间里精调淘汰逻辑更稳定。典型做法:
- 热区(占70%缓存容量):仅接受权重≥2的访问,淘汰严格按LFU-LRU混合策略
- 冷区(占30%):接收所有访问,但采用改进Clock算法(带引用位+修改位),且TTL默认设为60秒
- 冷区中的页若在存活期内被再次访问(且权重≥2),自动晋升至热区
这种设计天然抑制扫描类查询污染热数据,也避免因冷数据长期滞留导致热数据被迫驱逐。SQLite的Pager模块、TiDB的Region Cache均采用类似分层思路。










