缓存更新策略
一、前言
在真实业务中,数据库的数据会频繁发生变化,而缓存中的数据则是数据库数据的副本。如何保证缓存与数据库的数据一致性,是缓存使用中必须解决的核心问题。
常见的缓存更新策略可分为三大类:
-
内存淘汰:Redis 自带的内存淘汰机制,当内存达到 maxmemory 上限时,自动按规则淘汰部分 key,是一种被动的、无需人工参与的手段。
-
超时剔除:给缓存设置过期时间(TTL),到期自动删除。这是最简单有效的最终一致性保证,任何缓存数据都应该设置合理的过期时间。
-
主动更新:当数据库数据发生变化时,由开发者编码主动更新或删除缓存。这是保证高一致性场景下数据同步的核心手段。
不同的业务场景对一致性的要求不同:
-
高一致性:如金融、交易、库存扣减等,缓存与数据库必须保持近乎实时一致。
-
低一致性:如文章阅读量、点赞数、商品评价等,可以接受短暂的不一致,甚至允许一定程度的丢失。
二、主动更新策略
主动更新主要有三种实现模式:
-
由缓存的调用者,在更新数据库的同时更新缓存(可控性最高,也是最常用的方式)
-
缓存与数据库整合为一个独立的服务,由该服务内部维护一致性,调用者无需关心缓存同步细节(如 Tair、Aerospike 等部分特性)。
-
调用者只操作缓存,由另一个异步线程负责将缓存数据持久化到数据库,保证最终一致(如先写 Redis 再异步落库)。
异步更新的好处:可以合并多次写操作,减轻数据库压力。
异步更新的坏处:存在数据丢失风险(如进程崩溃导致缓冲区内数据未落库);实现复杂度高,需要处理消息顺序、幂等、重试等一系列分布式问题。
三、操作缓存和数据库需要解决的三个问题
在实现"更新数据库 + 更新缓存"这一主动策略时,必然面临三个关键选择。
1.删除缓存还是更新缓存?
删除缓存优于更新缓存
- 更新缓存:每次更新数据库都更新缓存,无效写操作较多
- 删除缓存:更新数据库时让缓存失效,查询时再更新缓存
更新缓存操作的缺点:大量无效写------例如一个热点数据 1 分钟内更新了 100 次,但被查询可能只有 2 次,其余 98 次缓存更新都是浪费资源;若缓存结构复杂(如多表关联),每次重新构建成本也很高。
2.如何保证缓存与数据库操作的同时成功或失败?
- 单体系统:将缓存与数据库操作放在一个事务。若缓存删除失败,回滚数据库事务;或者利用 Spring 的 @Transactional + 缓存切面保证原子性。
- 分布式系统:可利用 TCC、Seata 等分布式事务框架,或者采用最终一致性方案:数据库更新后,发送 MQ 消息,由消费者异步删除缓存,利用消息队列的重试机制保证缓存删除成功
3.先操作缓存还是先操作数据库?
先操作数据库,再删除缓存更好
- 先删除缓存,再操作数据库:线程1 删除缓存成功,线程2 查询未命中缓存,读数据库得到旧数据,线程2 将读到的旧数据写入缓存,线程1 更新数据库完毕。
- 这样导致缓存中永远是旧数据,直到缓存过期或下次更新才会被修正

- 这样导致缓存中永远是旧数据,直到缓存过期或下次更新才会被修正
- 先操作数据库,再删除缓存:线程1来查询是缓存刚好失效,线程1 从缓存查询未命中,读数据库得到旧数据,线程2 更新数据库成功,删除缓存,最后线程1 将旧数据写入缓存。
- 同样会导致缓存中短暂存在旧数据,直到下一次更新或缓存过期

先操作数据库,再删除缓存发生异常的情况概率要小一些,要满足线程1来查询时恰好缓存失效,线程1写缓存的操作要在另一个线程执行完毕之后,但是写缓存的操作大多是微秒级别,速度很快,而数据库的更新往往是毫秒级甚至更长。这种时间窗口极窄。
对于这种问题,可以通过给缓存设置一个合理的超时时间(TTL) 来解决。这个超时时间设置在缓存数据上(例如 SET user:1 value EX 1800),即使出现了脏数据,最多存活 30 分钟就会被自动清除,保证最终一致性。
四、缓存更新的最佳实践
采用主动更新数据库和缓存的方案,由缓存的调用者,在更新数据库的同时更新缓存。
对于读操作:
- 缓存命中 → 直接返回。
- 缓存未命中 → 查询数据库,将结果写入缓存,并设定合理的超时时间(如 30~60 分钟,根据业务容忍度调整)。
对于写操作:
- 先写数据库(更新/删除/插入)。
- 再删除对应的缓存(注意缓存 key 的粒度,确保删干净)。
- 若对一致性要求极高,可将数据库操作与缓存删除放入同一事务;若删缓存失败,考虑通过 MQ 异步重试。