上一篇我们在 缓存雪崩,穿透,击穿中讲到了为什么使用缓存,以及使用缓存可能会遇到哪些问题?其中就有缓存和数据库双写一致性问题。那什么是缓存和数据库双写一致性问题呢?是什么导致了一致性的问题呢?
什么是缓存和数据库双写一致性问题?
数据库中的数据和缓存中的数据不一致
如果我们只是读操作,肯定不会存在缓存和数据库双写一致性问题。但是如果更新或者删除操作呢?
是什么导致了数据库和缓存双写一致性问题?
我们知道执行一个更新操作花费的时间远远大于一个读操作花费的时间。更新操作,我们是先更新数据库呢还是先更新缓存呢?
如果两步操作符合原子性,要么同时成功,要么同时失败,则不存在一致性的问题,如果原子操作被破坏了,则会发生数据库和缓存双写一致性问题。
这两步操作并没有谁先谁后是没关系的,破坏原子性可能会存在以下四种情况。
- 操作数据库成功,操作缓存失败。
- 操作缓存成功,操作数据库失败。
- 操作数据库失败,操作缓存成功。
- 操作缓存失败,操作数据库成功。
无论是先操作数据库还是先操作缓存,如果第一步失败了(第3,4 种情况),直接报异常,第二步也就不执行了,所以我们完全可以忽略第3,4 种情况。
补充
:对于操作缓存,有更新和删除两种,少使用更新操作,有时候更新一个key所对应的value,这个vaule 值是经过多个表联查计算得出的结果,这个操作是非常慢的,频繁更新,更影响性能,其次该key单位时间内被读取的次数比较少。与其每次更新缓存,倒不如在需要读的时候,直接删除缓存,访问的缓存没有,此时就会访问数据库,然后重新计算一次即可。所以后面讲述的时候,更新操作统称删除操作。(用的时候再去查最新的值,这是一种懒加载的计算思想)
情况一: 先更新数据库成功,删除缓存失败。(Cache Aside Pattern设计模式)
更新数据库成功,更新缓存失败,此时缓存中的数据为旧数据。
并发场景下可能会出现一下问题:
- 缓存失效
- 线程A查询数据库,得一个旧值
- 线程B将新值写入数据库(写数据库操作花费时间比读操作长,写操作需要锁表)
- 线程B删除缓存
- 线程A将查到的旧值写入缓存
上述双写一致性问题发生的概率较小,既要刚好缓存失效,还并发这写操作。且读操作在写操作之前,又必须比更新缓存操作晚。
Cache Aside Pattern
如何避免删除缓存失败呢?
- 重试删除操作,直到成功
- 将删除的 key 发送到消息队列
- 采用消息队列,消费消息,获取要删除的key
情况二: 先删除缓存成功,更新数据库失败。
并发场景下可能存在以下情况:
- 线程A删除了缓存
- 线程B查询,缓存不存在,去数据库查询,得到旧值
- 线程B将旧值写入缓存
- 线程A将新值写入数据库
解决
: 采用消息队列,将 删除缓存,修改数据库,读缓存三步操作放入队列,实现串行化。
比较
先更新数据库,在删缓存,高并发请求下,可以缓解数据库压力,性能好,但是拿到的数据时旧数据。
先删缓存,后更新数据库,高并发请求下,访问数据库,数据库压力大,性能差,但是拿到的数据时新数据。