缓存失效需统一用time.time()计算ttl,避免与datetime.now()混用;lru_cache不支持时间失效,须手动管理;redis选expire或expireat要区分相对/绝对时间;数据库缓存必须写成功后再删。

缓存失效时,time.time() 和 datetime.now() 别混用
时间戳不一致会导致缓存提前或延迟失效,尤其在跨系统、跨时区或高并发场景下。Python 的 time.time() 返回浮点秒(UTC 时间),而 datetime.now() 默认本地时区、精度到微秒,两者数值不可直接比较。
- 统一用
time.time()做 TTL 计算:它轻量、无时区歧义、和大多数缓存库(如functools.lru_cache扩展、redis-py的expire)底层逻辑对齐 - 若必须用
datetime,务必转成 UTC 并用timestamp()方法归一:例如datetime.now(timezone.utc).timestamp() - 避免在缓存键中嵌入
datetime.strftime()字符串——格式化会丢失精度,且字符串比较无法替代数值 TTL 判断
functools.lru_cache 无法按时间自动失效,得自己包一层
lru_cache 只认调用参数和内存占用,完全不感知时间。所谓“过期”只是错觉,实际是靠 maxsize 淘汰旧项,和业务要求的“5 分钟后强制刷新”无关。
- 简单方案:用
cache = {}+last_updated = {}手动管理,每次读前检查time.time() - last_updated[key] > 300 - 进阶方案:封装成装饰器,把
ttl当参数传入,内部维护(value, timestamp)元组,而非裸值 - 注意:多线程下需加
threading.Lock,否则两个线程同时发现过期、同时重算,可能造成重复执行和覆盖
Redis 缓存失效,EXPIRE 和 EXPIREAT 选哪个
本质区别在于:一个传相对秒数,一个传绝对时间戳(秒级 Unix 时间)。选错会导致缓存永不过期或立刻消失。
- 用
EXPIRE:适合“从现在起 60 秒后失效”,命令为redis_client.expire(key, 60) - 用
EXPIREAT:适合“精确控制在某个时刻失效”,比如配合定时任务清理,命令为redis_client.expireat(key, int(time.time()) + 60) - 常见错误:误把
EXPIREAT当成毫秒时间戳传入——Redis 的EXPIREAT只接受秒级时间戳,传毫秒会变成 1970 年过期 - 性能提示:批量 key 失效优先用
redis_client.expireat()配合管道(pipeline),比循环调用expire快 5–10 倍
数据库查询结果缓存,失效时机卡在“写操作之后”还是“写操作之前”
答案是:必须在写操作**成功提交后**再删缓存,否则会出现“缓存未删、DB 已改”的短暂不一致,用户可能读到脏数据。
立即学习“Python免费学习笔记(深入)”;
- 典型错误模式:先
delete_cache(key),再db.update(...)—— 若 DB 更新失败,缓存已空,下次读触发重建,但数据其实没变,白白浪费资源 - 正确顺序:DB 写成功 →
commit()返回 → 再delete_cache(key) - 更稳做法:把缓存删除放进事务后置钩子(如 SQLAlchemy 的
after_commit),或用消息队列异步清理,避免 DB 事务阻塞缓存操作 - 注意:不要依赖“更新 DB 同时更新缓存”,双写一致性极难保证,删缓存才是最简单可靠的失效策略
缓存失效不是时间到了就自动发生的事,它是你主动控制的一次操作——发生在哪一行代码、用什么时间基准、在事务的哪个阶段,每一步都直接影响数据是否可信。










