缓存和数据库一致性如何保证
结论:缓存和数据库一致性没有银弹,生产上默认选「先更新数据库,再删除缓存」的 Cache Aside(旁路缓存:读时回源、写时更新库再删缓存),再用 TTL(缓存存活时间)、延迟双删、MQ(消息队列)或 binlog(MySQL 二进制日志)订阅补偿,把不一致窗口压到毫秒级到秒级;要求强一致的业务,不要让缓存参与关键读写。
缓存和数据库有哪四种经典更新策略?
结论:读多写少选 Cache Aside,写多且并发低可考虑 Write Through,能接受丢数据才用 Write Behind。
- 先更新数据库,再删除缓存:最常用。不一致窗口只出现在“删缓存失败到 TTL 到期”之间。
- 先删除缓存,再更新数据库:并发读会在数据库更新前回填旧值,风险高。
- 先更新数据库,再更新缓存:两个并发写可能用旧值覆盖新值。
- 先更新缓存,再更新数据库:数据库失败时缓存直接变脏数据。
Cache Aside 的读流程是:查 Redis,命中返回;未命中查 MySQL,写回 Redis,并设置 TTL。写流程是:更新 MySQL,删除 Redis key。删除比更新缓存更稳,因为删除后下次读会回源拿最新值,更新缓存则可能写入计算错误的中间值。
为什么推荐“先更新数据库,再删除缓存”?
结论:该策略把数据库当唯一事实源,缓存只做加速,异常时最多读到旧值,不会读到数据库里不存在的值。
具体步骤:
- 写请求更新 MySQL,提交事务。
- 删除 Redis 中对应 key,例如
DEL product:1001。 - 读请求未命中时,从 MySQL 查询并
SETEX product:1001 600 value,TTL 设为 600 秒。
它仍有三个坑:删缓存失败、MySQL 主从延迟导致回源读到旧数据、并发读在删除后立刻回填旧值。解决办法分别是重试与补偿、强制写后读主库、延迟双删。
延迟双删怎么做,延迟时间设多少?
结论:延迟双删适合“更新数据库后立即删缓存仍被旧读回填”的场景,第二次删除延迟建议 500ms 到 1s,必须大于一次读请求回源并写缓存的总耗时。
步骤:
- 先删除缓存。
- 更新数据库。
- 延迟 500ms。
- 再删除缓存。
更稳的版本是:更新数据库后删缓存,同时发一条延迟消息,500ms 或 1s 后再删一次。注意延迟时间要覆盖 MySQL 主从复制延迟加读请求回源写入 Redis 的耗时。最后再用 TTL 兜底,例如 10 分钟,保证极端情况下脏数据不会永久存在。
缓存删除失败怎么办?
结论:删除失败不能只靠同步重试,必须落补偿,否则缓存会一直脏到 TTL 过期。
方案 A:本地消息表。更新 MySQL 的同一个事务里写入 outbox 表,后台任务投递 MQ,消费者删除 Redis。删除失败重试 3 次,退避间隔 1s、2s、4s。删除操作天然幂等(同一操作执行多次结果一致),重复消费不会出错。
方案 B:订阅 MySQL binlog。用 Canal 1.1.5 解析 row 模式 binlog,投递 Kafka 或 RocketMQ,消费者按“表名 + 主键”删除 Redis key。优点是业务代码无侵入,数据库变更和缓存删除最终一致。注意 MQ 消费者要幂等,binlog 顺序要按主键分区。
并发读写导致旧值覆盖,怎么解决?
结论:用版本号或分布式锁能避免旧读回填旧值,但分布式锁会降低吞吐。
版本号方案:数据库记录带 version。更新时执行 UPDATE ... SET version = version + 1 WHERE id = ?。读缓存时发现 value.version 小于数据库当前版本,就丢弃缓存并回源。写请求删缓存前检查版本,旧版本请求直接丢弃。
分布式锁方案:对同一个 key 加 Redis 锁,命令是 SET lock:product:1001 1 NX PX 3000,写请求持锁更新数据库并删缓存,读请求回源也持锁。锁超时 3000ms,避免死锁。适合库存、余额等敏感字段。
强一致场景能不能用缓存?
结论:强一致场景不要用缓存,或者把缓存当只读副本并接受主从延迟。要求读己之写,写后读主库;要求线性一致(所有读都能看到最新写),读请求直接穿透到 MySQL。
如果必须用 Redis 扛读,可以把 TTL 设到 10 秒以内,并接受秒级不一致。库存扣减可用 Redis Lua 原子扣减,再通过 MQ 异步落库,业务上做对账补偿。账户余额这类场景,直接读主库,不用缓存。
最终选型清单
普通商品详情:Cache Aside + TTL 600 秒 + 删除失败重试 3 次。秒杀库存:Redis Lua 原子扣减 + MQ 异步落库 + 对账。订单状态:Canal 订阅 binlog + MQ 删缓存,延迟控制在 100ms 内。账户余额:不用缓存,强一致读主库。
缓存和数据库一致性本质是业务能容忍多久的旧数据。数据库为准,缓存失效为主,补偿和 TTL 兜底,监控不一致 key 并告警,才是可落地的方案。
原文链接:https://www.gj0.com/thread-449.html
转载请注明出处并保留本声明;内容仅代表作者观点,与本站立场无关。若本文涉嫌侵权,请联系本站处理。