缓存与数据库一致性
一致性问题来自哪里
在引入了缓存的系统中,数据会同时存储在缓存(Redis) 和数据库(MySQL) 中。当数据发生更新时,需要同时更新两个存储层。由于两者是独立的存储系统,不可能做到”同时”更新,因此会出现缓存与数据库数据不一致的情况。
不一致的场景分析
写操作的时序问题
假设有两个并发操作:写操作 A 和写操作 B,各自更新同一条数据:
时间线:
T1: 操作 A 写数据库 → 写成功
T2: 操作 B 写数据库 → 写成功
T3: 操作 A 更新缓存 → 缓存中是 A 的数据(旧数据 B 的更新被覆盖)
结果:缓存中保存的是”A 的数据”,但数据库中保存的是”B 的数据”,不一致。
读写并发问题
时间线:
T1: 读请求 → 缓存未命中 → 查数据库(拿到旧数据 V1)
T2: 写请求 → 更新数据库为 V2 → 更新缓存为 V2
T3: 读请求 → 将读到的旧数据 V1 写入缓存
结果:缓存中是 V1(旧数据),数据库中是 V2(新数据),不一致。
一致性的级别
在缓存场景中,通常追求的是最终一致性而非强一致性:
| 一致性级别 | 说明 | 是否可以接受 |
|---|---|---|
| 强一致性 | 写入后立即读取到最新值 | ❌ 成本过高 |
| 最终一致性 | 经过短暂延迟后读取到最新值 | ✅ 缓存场景 |
| 弱一致性 | 可能永远读不到最新值 | ❌ 不可接受 |
影响一致性的因素
1. 更新策略
不同的更新策略直接影响一致性:
| 策略 | 一致性风险 | 说明 |
|---|---|---|
| 先更新缓存再更新数据库 | 极高 | 缓存成功、数据库失败则数据永久不一致 |
| 先更新数据库再更新缓存 | 中 | 并发更新可能导致覆盖 |
| 先删缓存再更新数据库 | 低 | 适合读多写少场景 |
| 先更新数据库再删缓存 | 低 | 最推荐(Cache Aside 模式) |
2. 过期时间
没有设置过期时间的缓存,不一致问题会永久存在。设置合理的过期时间是保证最终一致性的最后一关——即使缓存没有被清除,过期后也能自动恢复。
3. 并发请求
并发越高,不一致的概率越大。需要结合分布式锁或版本号来控制并发冲突。
一致性的代价
性能 vs 一致性
越是追求一致性,对性能的影响就越大:
← 性能越好 | 一致性越好 →
策略:不缓存 > 缓存无过期 > 缓存有过期 > 删缓存 > 延迟双删 > 强同步
场景权衡
数据一致性要求高(如金融、订单):
→ 减少缓存的使用
→ 增加过期时间检查
→ 使用版本号或 CAS 乐观锁
数据一致性要求低(如新闻、资讯):
→ 大胆使用缓存
→ 过期时间可适当延长
→ 简单的一删一改即可
业务上可以接受的”不一致”
在大多数业务场景下,缓存与数据库的短暂不一致是可以接受的:
- 用户信息:更新后几秒钟显示旧头像,没问题
- 商品库存:几秒钟的库存差异,可以接受
- 文章阅读量:几分钟内的差异,完全 OK
- 配置项:更新后立即生效的要求可能较高
需要被”容忍”的通常是秒级的不一致,越敏感的业务越要注意:
不可接受的场景:
- 支付金额、账户余额(不能容忍)
- 订单状态变更(不能容忍)
- 秒杀库存扣减(不能容忍)
总结
缓存与数据库的一致性问题是分布式系统的本质困难——两个独立的存储系统无法做到原子性更新。不存在一个”完美”的解决方案可以做到:高性能、强一致、零延迟。实际工程中,正确的做法是:
- 理解业务能接受什么样的不一致(秒级?毫秒级?)
- 选择合适的更新策略(Cache Aside 是首选)
- 设置合理的过期时间作为兜底
- 用最终一致性接受现实——在缓存场景中,强一致性是奢望
在面试中,直接承认”无法做到强一致,只能追求最终一致”反而是加分项,说明你理解分布式系统的本质。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END


暂无评论内容