被先更新数据库,再删除缓存问题数据不一致原因搞大了头

这篇文章不会去讨论是先删缓存还是先更新数据库这个问题,而是只聊其中一个,个人采用的,我觉得或者大部分人都觉得最优的一个的方案,即
先更新数据库,再删除缓存

然而再聊到这个方案的弊端的时候,充斥着大量的文章来解释这个方案带来的缓存不一致的原因如下,我看了半天尽量用自己理解下来的话说的清楚点。

  1. 线程1执行查询操作, 未命中缓存,然后查询数据库得到db中结果。
  2. 线程2执行更新数据库操作, 然后删除缓存, db中目前是最新值,但是和1查询的结果是不一致的。
  3. 线程1用查询到的db的值来缓存到数据库,由于线程2删除缓存在线程1查询之后,因此缓存中的值为旧值,从而造成数据不一致
  4. 总结:当查询的线程结果晚于更新缓存的时候,非常极致的情况下,会出现这种情况造成缓存不一致。

不太明白这种情况出现的原因,线程1是读请求, 线程2是写请求,那么在线程1获取读锁的时候,线程2由于是要获取写锁,必然是要一直阻塞的,一直到写结束释放锁之后,线程2才有资格去重新获取锁,我想知道它哪来的极限情况会比读请求慢啊。
你要是非要侵入写缓存代码,在数据库查询之后,写了一堆业务耗时代码,最后才去缓存结果,当我没说。

现在探讨一下这个方案可能的弊端:

  1. 线程1写请求修改数据库,删除缓存后事务还没提交
  2. 线程2读请求无法命中缓存,然后去db中查询数据即查询到线程1更新前的旧值,然后设置到缓存中,线程1提交事务,这时候缓存和数据库是不一致的
  3. 线程1提交事务后,没有其它逻辑去处理线程2造成的脏数据,最终缓存不一致形成
  4. 关于第1步删除缓存后事务还未提交的可能性有多高的问题。我觉得如果一个方法里最终只牵扯到一次的更新并有对应的缓存操作,那么在最后一步执行可能性是非常非常低的。但如果一个方法里,有多个这样的操作, 那么就会增大这种可能。尤其是采用了spring cache 这种注解方式,在方法调用之后就会直接执行evit操作,所以可能性会更大一些。

那么针对这个方案有没有什么好的解决方案,经常看到过延时双删的策略,但基本都把这个策略用在先删除缓存,再更新数据库, sleep一段时间之后再次删除缓存,就可以把这段时间造成的脏数据清除掉。然而我没有明白,这更新之前删除缓存的意义在哪里?如果最后sleep之后的删除失败了,那么这个方案和先删除缓存再更新数据库这个方案有什么区别呢?

所以这个双删,针对上面说的事务未提交的原因,其实是不是每次牵扯到因为更新数据库而删除缓存的情况下,每次都直接删两次,一次是即时删除,一次是异步延迟删除。这个异步的时间取决于上一次更新后事务未提交的窗口期,所以这个时间不需要太长,但是也不必过去太短。定个几秒钟,我觉得是个比较合适的时间。当然如果延迟删除失败了,这又会牵扯到更加复杂的逻辑。总而言之,我觉得,这个方案的弊端是上面那个事务未提交窗口期造成的,那么其实可能性已经极大的降低了。再延迟删除一次,我觉得一般的业务应该都能够满足了。

当然,可能是我没有理解好这个方案的真实弊端,也没有理解到网上的那个点,但这个是我目前的认知了,希望能早点拨开云雾。

你可能感兴趣的:(redis,缓存不一致问题)