标准LRU在SQL数据库缓存中易失效,因其“刚访问即热”假设不成立:全表扫描、临时JOIN等一次性查询会挤占热点数据,且LRU无法区分同页内不同深度的访问模式。
标准LRU(最近最少使用)假设“刚被访问过的数据,近期还会被访问”,但SQL查询场景下这个假设常不成立。比如全表扫描会把大量冷数据推入缓存并挤走热点行;又如临时JOIN中间结果、一次性报表查询,它们只用一次却长期占据缓存空间。更关键的是,数据库缓存单位通常是数据页(Page)或块(Block),而非单行,而不
同查询对同一页面的访问模式差异大——有的只读首几行,有的遍历全部,LRU无法区分这种“访问深度”差异。
单纯看频率(LFU)易受短期突发查询干扰,单纯看时间(LRU)忽略长期热度。实用做法是给每个缓存项维护两个计数器:访问频次(counter)和最后访问时间戳(last_access)。淘汰时优先淘汰(counter × α + (now − last_access) × β)值最大的项,其中α、β为可调权重(例如α=1, β=0.01)。这样既保留高频热数据,又及时清理长时间未触达的低频页。
shared_buffers虽未直接暴露该算法,但其clock-sweep机制已隐含类似思想不是所有访问都应被同等对待。SELECT主键查询、索引范围扫描、聚合GROUP BY、全表扫描,对缓存价值的贡献差异显著。可在查询解析阶段打标,赋予不同权重:
缓存项的实际热度 = 原始counter × 查询权重。这要求缓存层与查询优化器轻耦合,MySQL 8.0+可通过Query Rewrite插件+自定义UDF初步支持。
把一块内存硬切成“热区”和“冷区”比在统一空间里精调淘汰逻辑更稳定。典型做法:
这种设计天然抑制扫描类查询污染热数据,也避免因冷数据长期滞留导致热数据被迫驱逐。SQLite的Pager模块、TiDB的Region Cache均采用类似分层思路。