MySQL四种写操作导致数据不一致策略(未完结)

先更新数据库再更新缓存

优点

简单,代码逻辑直观

缺点

  1. 线程安全问题:多个线程同时更新数据>时,可能会导致缓存与数据库不一致
  • 线程A更新了数据库
  • 线程B也更新了数据库,并且先于线程A更新了缓存
  • 线程A随后更新缓存,此时缓存中的数据会变成线程A的旧数据,而数据库中是线程B的新数据,导致数据不一致
  1. 并发读写问题:缓存失效,A读取缓存,此时缓存失效,进而读取数据库的值,在A更新缓存之前,就在此时线程B更新了数据库的值,并且将缓存的值进行了更新,随后A又进行了缓存的更新,那么线程A写入的旧值会覆盖线程B的新值。所以此时数据库是更新好的值,而缓存是旧值,就会出现数据不一致的情况

(1. 2. 说的差不多:A.mysql —> B.mysql —> B.redis —> A.redis)

  1. 性能浪费:
  • 写多读少,缓存被频繁更新但很少被读取
  • 缓存数据需要通过复杂计算生成,写入缓存会增加不必要的开销

改进策略

先更新缓存再更新数据库

优点

缓存更新快,能够更快速的响应服务器的请求,更新速度快

缺点

  1. 有数据库更新失败风险:导致数据不一致
  2. 线程安全问题:(A.redis —> B.redis —> B.mysql —> A.mysql ))
  3. 写入性能瓶颈:每次写操作都需要同步更新缓存和数据库,可能导致写入性能下降,尤其是当数据库操作较慢时

先删除缓存再更新数据库

优点

  • 能够有效避免缓存当中的旧数据被读取

  • 实现简单(删除缓存是一个简单操作)

缺点

  1. 有可能缓存删除失败导致数据不一致
  2. 存在长期的脏数据:

​ 假设线程A执行“先删除缓存再更新数据库”的操作,同时线程B执行读操作,可能的执行顺序如下:

  • 线程A删除缓存。
  • 线程B查询缓存,发现缓存已删除。
  • 线程B查询数据库,获取旧数据。
  • 线程B将旧数据写入缓存。
  • 线程A更新数据库。

​ 此时,数据库中存储的是新数据,而缓存中存储的是旧数据,导致数据不一致。这种情况下,缓存中的旧数据被称为“脏数据”。

改进策略

延时双删:解决脏数据

先删除缓存 —> 更新数据库(设置延时) —> 删除缓存

示例场景

假设线程A和线程B同时执行以下操作:

  1. 线程A
    • 删除缓存。
    • 更新数据库。
    • 延时1秒后再次删除缓存。
  2. 线程B
    • 在线程A删除缓存后,线程B查询缓存发现数据不存在。
    • 线程B查询数据库,获取旧数据并写入缓存。

分析

  • 在线程A更新数据库之前,线程B可能已经将旧数据写入缓存。
  • 线程A更新数据库后,旧数据仍然存在于缓存中。
  • 线程A延时1秒后再次删除缓存,清理了线程B回填的旧数据。
  • 此时,缓存中没有数据,后续的读操作将直接从数据库获取最新数据。

缺陷

在删除缓存之后 更新数据库之前这段时间内过来的读请求 也会把旧数据写回缓存中。此时数据就会不一致,不一致的时间取决于数据库的更新时间

注意:

延时时间需要根据业务的请求时间(读请求)来定,否则也会存在在第二次删除之后读请求还未结束,又把旧数据写回缓存,导致依然存在脏数据的问题。

先更新数据库再删除缓存

优点:

  • 数据不一致的时间窗口短

  • 有效避免了缓存击穿

    由于删除操作是在数据库更新后执行的,因此再有大量请求时,在第一个读请求后就会写回缓存,后续读取缓存即可,就不会有大量的请求打到数据库上

缺点

  • 也会产生一部分的数据不一致的时间,但是会大大缩短

数据不一致的时机:更新完数据库后,删除缓存前。这一段时间是数据不一致的,但是由于缓存删除的时间要比数据更新的时间快多了,因此数据不一致的时间窗口非常短暂。

  • 也会存在缓存删除失败

一定要考虑业务场景和成本再进行选择这四种策略,一般朴素的就够用, 因为本身数据库的更新就比写入缓存时间长

如何解决缓存删除失败?


MySQL四种写操作导致数据不一致策略(未完结)
http://yjmanman.github.io/2025/02/01/四种写操作导致数据不一致策略/
作者
YuJia
发布于
2025年2月1日
许可协议