@lru_cache 总是 miss 的主因是参数不可哈希或缓存配置不当:可变对象(如 list、dict)导致跳过缓存;maxsize 过小引发频繁淘汰;缓存作用域错配(如实例方法)造成隔离;时间敏感逻辑误用缓存。

为什么 @lru_cache 总是 miss?
缓存命中率低,最常见原因是被缓存的函数参数“看起来一样,实际不同”。@lru_cache 默认只对位置参数和关键字参数做浅层哈希,一旦传入可变对象(比如 list、dict、自定义类实例),就会直接跳过缓存——不是没命中,而是压根没进缓存逻辑。
- 常见错误现象:
TypeError: unhashable type: 'list'不报错但缓存失效;或函数反复执行,cache_info()显示hits=0 - 使用场景:高频调用、参数含配置字典或查询条件列表时尤其明显
- 实操建议:把
list改成tuple,dict改成frozenset(dict.items())或用json.dumps(..., sort_keys=True)生成稳定字符串键 - 注意:
lru_cache(maxsize=None)不能解决哈希问题,只是不限制条目数
maxsize 设太小导致缓存频繁淘汰
默认 maxsize=128,看着不小,但若函数调用参数组合多(比如分页查询中 page=1、page=2… 连续调用),很快就会被 LRU 策略踢掉旧项,新请求又得重算。
- 常见错误现象:缓存
hits有但不高,currsize常接近maxsize,且misses持续增长 - 参数差异:设为
None表示无上限;设为0则完全禁用缓存(不推荐用于诊断) - 性能影响:
maxsize过大可能吃内存,尤其参数本身较大(如传入大numpy.ndarray);过小则失去缓存意义 - 实操建议:先用
cache_info()观察真实currsize和访问模式,再按需调整;对参数空间有限的函数(如状态码映射),maxsize=32就够了
缓存未覆盖实际热点路径
写了个 @lru_cache,但业务里真正被反复调用的其实是另一个封装函数,或者缓存函数被嵌套在类方法里、每次创建新实例导致缓存隔离——缓存存在,但根本没被复用。
- 常见错误现象:单测里命中率 100%,线上却几乎不 hit;
cache_info()在不同地方调用结果不一致 - 使用场景:Django/Flask 视图中直接装饰工具函数没问题,但若放在
self.方法上,每个实例有独立缓存,等同于没缓存 - 实操建议:优先缓存在模块级函数上;若必须用实例方法,改用
functools.cached_property(Python 3.8+)或手动管理共享缓存字典 - 兼容性注意:类方法加
@classmethod+@lru_cache是安全的,因为类是单例
时间敏感型逻辑误用缓存
缓存本意是省计算,但若函数依赖当前时间、随机值、数据库最新状态等动态输入,缓存反而会返回过期结果——这不是命中率低,而是“命中了错的”。
立即学习“Python免费学习笔记(深入)”;
- 常见错误现象:函数结果明明该变却不变;日志显示调用时间跨度大,但返回值相同
- 使用场景:生成 token、查实时库存、读取配置中心、调用
datetime.now()的函数 - 实操建议:检查函数是否隐式依赖外部状态;必要时把动态因子作为显式参数传入(如
get_price(item_id, as_of=datetime.now())),并确保该参数参与缓存键计算 - 容易被忽略的点:哪怕只有一行
time.time(),整个函数也不适合无脑缓存;宁可加一层带 TTL 的缓存(如functools.cache配合手动刷新),也别让缓存掩盖数据时效问题
缓存不是开关,是精细调节阀。参数哈希、大小阈值、作用域边界、数据新鲜度——四个地方只要一个没对齐,命中率就掉得无声无息。










