MySQL四种写操作导致数据不一致策略(未完结)
先更新数据库再更新缓存
优点
简单,代码逻辑直观
缺点
- 线程安全问题:多个线程同时更新数据>时,可能会导致缓存与数据库不一致
- 线程A更新了数据库
- 线程B也更新了数据库,并且先于线程A更新了缓存
- 线程A随后更新缓存,此时缓存中的数据会变成线程A的旧数据,而数据库中是线程B的新数据,导致数据不一致
- 并发读写问题:缓存失效,A读取缓存,此时缓存失效,进而读取数据库的值,在A更新缓存之前,就在此时线程B更新了数据库的值,并且将缓存的值进行了更新,随后A又进行了缓存的更新,那么线程A写入的旧值会覆盖线程B的新值。所以此时数据库是更新好的值,而缓存是旧值,就会出现数据不一致的情况
(1. 2. 说的差不多:A.mysql —> B.mysql —> B.redis —> A.redis)
- 性能浪费:
- 写多读少,缓存被频繁更新但很少被读取
- 缓存数据需要通过复杂计算生成,写入缓存会增加不必要的开销
改进策略
先更新缓存再更新数据库
优点
缓存更新快,能够更快速的响应服务器的请求,更新速度快
缺点
- 有数据库更新失败风险:导致数据不一致
- 线程安全问题:(A.redis —> B.redis —> B.mysql —> A.mysql ))
- 写入性能瓶颈:每次写操作都需要同步更新缓存和数据库,可能导致写入性能下降,尤其是当数据库操作较慢时
先删除缓存再更新数据库
优点
能够有效避免缓存当中的旧数据被读取
实现简单(删除缓存是一个简单操作)
缺点
- 有可能缓存删除失败导致数据不一致
- 存在长期的脏数据:
假设线程A执行“先删除缓存再更新数据库”的操作,同时线程B执行读操作,可能的执行顺序如下:
- 线程A删除缓存。
- 线程B查询缓存,发现缓存已删除。
- 线程B查询数据库,获取旧数据。
- 线程B将旧数据写入缓存。
- 线程A更新数据库。
此时,数据库中存储的是新数据,而缓存中存储的是旧数据,导致数据不一致。这种情况下,缓存中的旧数据被称为“脏数据”。
改进策略
延时双删:解决脏数据
先删除缓存 —> 更新数据库(设置延时) —> 删除缓存
示例场景
假设线程A和线程B同时执行以下操作:
- 线程A:
- 删除缓存。
- 更新数据库。
- 延时1秒后再次删除缓存。
- 线程B:
- 在线程A删除缓存后,线程B查询缓存发现数据不存在。
- 线程B查询数据库,获取旧数据并写入缓存。
分析:
- 在线程A更新数据库之前,线程B可能已经将旧数据写入缓存。
- 线程A更新数据库后,旧数据仍然存在于缓存中。
- 线程A延时1秒后再次删除缓存,清理了线程B回填的旧数据。
- 此时,缓存中没有数据,后续的读操作将直接从数据库获取最新数据。
缺陷
在删除缓存之后 更新数据库之前这段时间内过来的读请求 也会把旧数据写回缓存中。此时数据就会不一致,不一致的时间取决于数据库的更新时间。
注意:
延时时间需要根据业务的请求时间(读请求)来定,否则也会存在在第二次删除之后读请求还未结束,又把旧数据写回缓存,导致依然存在脏数据的问题。
先更新数据库再删除缓存
优点:
数据不一致的时间窗口短
有效避免了缓存击穿
由于删除操作是在数据库更新后执行的,因此再有大量请求时,在第一个读请求后就会写回缓存,后续读取缓存即可,就不会有大量的请求打到数据库上
缺点
- 也会产生一部分的数据不一致的时间,但是会大大缩短
数据不一致的时机:更新完数据库后,删除缓存前。这一段时间是数据不一致的,但是由于缓存删除的时间要比数据更新的时间快多了,因此数据不一致的时间窗口非常短暂。
- 也会存在缓存删除失败
一定要考虑业务场景和成本再进行选择这四种策略,一般朴素的就够用, 因为本身数据库的更新就比写入缓存时间长
如何解决缓存删除失败?
MySQL四种写操作导致数据不一致策略(未完结)
http://yjmanman.github.io/2025/02/01/四种写操作导致数据不一致策略/