跳转至

Redis 缓存与数据库双写不一致

一、问题

写 DB 和写 Redis 不是原子操作,并发下会不一致。

线程 A: 更新 DB = 1
线程 B: 更新 DB = 2
线程 B: 删除 Redis
线程 A: 删除 Redis

时序问题导致 Redis 里是旧值。

二、常见方案

1. 先更新 DB,再删缓存(Cache Aside)

updateDB();
redis.del(key);

为什么删而不是更新? - 更新缓存可能并发写覆盖。 - 懒加载:下次读时再回源。

2. 延迟双删

redis.del(key);
updateDB();
Thread.sleep(500);
redis.del(key);   // 再删一次

第二次删是为了清掉"在读过程中被其他线程写回的旧值"。

缺点:sleep 时间不好定,阻塞线程。

3. 订阅 Binlog

Canal 监听 MySQL Binlog,异步删缓存。

MySQL Binlog → Canal → MQ → 消费者删 Redis

优点:业务代码无侵入。 缺点:增加 MQ 链路,延迟。

4. 分布式锁

把"更新 DB + 删缓存"串行化。

lock(key);
try {
    updateDB();
    redis.del(key);
} finally {
    unlock(key);
}

三、为什么不用先更新缓存

  • 缓存更新并发写容易覆盖。
  • 不是所有读都走缓存,更新缓存浪费。

四、一致性要求

  • 强一致:用分布式锁 + 串行化。
  • 最终一致:Canal + MQ,可接受秒级延迟。
  • 不要求:直接更新 DB,缓存 TTL 到期自动更新。

五、对比

方案 一致性 性能 复杂度
先更新 DB 再删缓存 最终一致
延迟双删 最终一致
Canal Binlog 最终一致
分布式锁 强一致

生产推荐

先更新 DB 再删缓存 + Canal 兜底。延迟双删是过渡方案。