从原理到实战,一篇彻底讲透数据库与缓存一致性问题,火爆了!

📅 2026/6/30 16:58:53
从原理到实战,一篇彻底讲透数据库与缓存一致性问题,火爆了!
在高并发系统中几乎都会采用 MySQL Redis 的架构来提升系统性能。Redis负责扛流量、提升查询速度MySQL负责数据持久化存储。但引入缓存的同时也带来了一个相对棘手的问题数据库更新了缓存没更新怎么办缓存更新了数据库没更新怎么办如何保证数据库与缓存中的数据一致性什么是数据库与缓存一致性Redis缓存与数据库双写一致性本质是异构存储系统的数据对齐问题缓存Redis与数据库如MySQL是两个独立的、无原生分布式原子性保证的存储系统在并发读写场景下因操作时序错位、执行失败、网络波动等因素导致缓存和数据库存储的数据出现差异最终引发业务脏读、数据错误。假设商品库存如下此时数据库与缓存的数据相同。此时用户修改了库存执行以下语句updateproductsetstock99whereid1001;如果因为某种因素没有将 Redis 中的数据更新导致数据库的数据是99而Redis中的数据是100那么用户读取缓存时得到的就是脏数据。这就是典型的缓存数据不一致。要解决数据不一致问题通常有以下4种方式先更新数据库再更新缓存在并发场景下假设有两个线程线程1和线程2同时并发更新同一条数据时由于网络延迟或线程调度可能出现以下执行时序线程1先将数据库中的数据更新为100线程2紧接着将数据库中的数据更新为99线程2动作极快顺手把缓存也更新成了99最后线程1把缓存更新成了100这就造成数据库的数据是99缓存的数据是100很明显数据库和缓存数据不一致问题。如果业务上由于缓存命中率要求必须使用更新缓存需要引入以下方案解决数据不一致问题分布式锁在更新缓存前加分布式锁如 Redisson确保同一时间只有一个请求能更新数据库缓存强制串行化。短过期时间给缓存设置较短的 TTL让不一致的脏数据能尽快失效。先更新缓存再更新数据库并发场景下同样的分析两个并发更新请求线程1和线程2请求线程1先将缓存中的数据更新为100。在请求线程1还没来得及更新数据库时线程2再将缓存中的数据更新为99。请求线程2顺手把数据库也更新成了99。最后请求线程1才执行数据库更新把数据库的数据更新成了100。先更新数据库再删除缓存在并发场景下读写时理论上存在一种极端的漏洞时序此时缓存中刚好没有该数据。请求线程1前来读取从数据库中查到旧值为100。在请求线程1还没来得及将旧值写入缓存之前请求线程2前来更新。请求线程2将数据库更新为99并执行删除缓存此时缓存本来就空删了个寂寞。最后线程1才写入缓存的值为100把刚才线程2更新的99写回了缓存。这就是著名的Cache Aside 旁路缓存写策略。要解决这个问题使用以下方式消息队列MQ重试机制如果删除缓存失败将要删除的 Key 写入 MQ由消费者尝试异步重试删除直到成功。订阅 MySQL binlog利用 Canal 等中间件伪装成 MySQL 从节点监听 binlog 变更一有更新就由专职服务异步解析并删除对应的 Redis 缓存。先删除缓存再更新数据库并发场景下在读写时这种方案的漏洞触发概率非常高请求线程1准备更新数据为100时先删除了缓存。此时请求线程2前来读取数据发现缓存未命中便去数据库中读取到了99。线程2将读到的99写入了缓存。最后线程1才更新数据库数据为100将其改为了新值。要解决这个问题也简单延迟双删redis.delKey(X);// 1. 先删缓存db.update(X);// 2. 更新库Thread.sleep(N);// 3. 强制休眠一段时间如 200msredis.delKey(X);// 4. 再次删除缓存阿里云参考文档https://developer.aliyun.com/article/1732763