@lru_cache默认无过期且不共享,易致内存溢出、数据不一致;应显式设maxsize、避免直接用于web视图、禁用不可哈希参数、勿缓存orm实例、多进程须改用redis等外部缓存。

缓存没设过期时间,@lru_cache 会吃光内存
python 的 @lru_cache 默认不设最大容量(maxsize=None),且完全不处理时间过期。一旦函数被高频调用、参数组合多(比如带用户 ID、时间戳、分页 offset),缓存项会无限增长,最终拖慢 GC、触发 OOM。
实操建议:
- 显式指定
maxsize:例如@lru_cache(maxsize=128),避免默认的“无上限”陷阱 - 别在长期运行的服务里(如 flask/fastapi 视图函数)直接套
@lru_cache,它绑定的是函数对象,跨请求不清理,也不感知上下文变化 - 如果真需要时间过期,换用
functools.cache不行——它连maxsize都不支持;得用cacheout或diskcache这类带 TTL 的库
functools.cache 看似简单,但参数必须可哈希
functools.cache 是 @lru_cache(maxsize=None) 的简化 alias,底层仍靠 hash() 做键。传入 dict、list、set、dataclass(未设 frozen=True)这类不可哈希对象,会直接抛 TypeError: unhashable type。
常见错误现象:本地测试用固定字符串没问题,一上生产传了 request.args(Flask 的 ImmutableMultiDict)就崩。
立即学习“Python免费学习笔记(深入)”;
实操建议:
- 检查所有参数类型:用
hash(arg)手动试一遍,尤其注意嵌套结构 - 把复杂参数转成规范字符串键:例如
json.dumps(sorted(request_args.items()), sort_keys=True),再喂给缓存函数 - 别依赖 ide 提示——
functools.cache不做运行时类型校验,错只在第一次调用时暴露
django/Flask 里混用 lru_cache 和数据库查询,结果不一致
缓存函数若封装了 Model.objects.get() 或 session.execute(),它的返回值是首次执行时的 ORM 实例或 Row 对象。后续调用返回的是同一个 Python 对象引用,而数据库可能已被其他进程/线程更新——你读到的是“旧状态+新内存地址”,看似没报错,数据却静默过期。
使用场景典型如:用户权限判断函数加了缓存,但管理员后台刚改完权限,前端刷新页面仍显示旧菜单。
实操建议:
- 缓存只存原始数据(如
user_id → {'is_admin': True, 'roles': [...]}),别缓存 ORM model 实例 - 如果必须复用对象,加一层浅拷贝:
return copy.deepcopy(cached_obj),但注意性能开销 - 更稳妥的做法:用框架原生缓存(如 Django 的
cache.get()+cache.set()),它们天然支持序列化/反序列化,也方便配 Redis 后端统一失效
多进程下 @lru_cache 完全不共享,还浪费内存
Python 多进程(multiprocessing、gunicorn worker、Celery task)中,每个子进程都有独立的 Python 解释器和内存空间。@lru_cache 缓存在各自进程内维护,彼此隔离。结果就是:10 个 worker 各自缓存同一份数据 10 次,内存翻 10 倍,且无法协同失效。
性能影响明显:本想靠缓存降 DB 压力,实际每进程都照查不误;更糟的是,你以为加了缓存就安全了,监控里 DB QPS 却纹丝不动。
实操建议:
- 确认部署模型:Gunicorn 用
--preload也不能共享lru_cache,因为 fork 后各进程仍初始化独立缓存 - 跨进程场景一律改用外部缓存:Redis、memcached,哪怕本地用
diskcache.Cache()也比进程内缓存强 - 如果只是想避免重复初始化(如加载大配置文件),用模块级变量 +
if 'config' not in globals():更轻量,且明确可控
事情说清了就结束。缓存不是开关,是状态管理——它在哪存、谁可见、何时失效,每个点都得对齐你的部署拓扑和数据一致性要求。