redis缓存一致性如何考虑数据库事务的影响?

redis缓存一致性方案,网上统一比较认同的为 延迟双删 方案。即:

写DB
删除缓存
延迟n秒
再次删除缓存

但所有文章都没有涉及到 数据库事务、 以及事务隔离性问题。
假如在一个数据库事务中,涉及到执行读写数据库操作 10 次,
那么问题是,删除缓存的时机是每次执行数据库操作之后即删除或更新缓存吗?
还是要先将事务内的数据库变动 缓存起来,等事务提交时,再一次性同步到底层redis?

在事务中更新缓存的话,带来的严重问题就是会破坏 数据库事务的隔离性,比如一般事务隔离性为读已提交,但事务中更新缓存,会导致事务隔离性变成读未提交,即事务回滚导致读到脏数据问题。
但如果缓存在整个事务结束时一次性同步redis的话, 由于事务执行时间远大于一次单独的SQL操作,
无疑会大大增加并发读写出现缓存和DB不一致的几率(即延迟双删要解决的问题)。

在实际项目中,大家都是怎么做的呢?比如要做一个查询缓存层的话
何时缓存数据、何时删除数据呢?

感谢分享!(比较奇怪,网上为何搜不到一点redis和db事务相关的内容?)

~~
2024-07-11补充:
忽然考虑到一个比较简单的方案,初步思路如下:

目标:一个通用的DB查询缓存层,旨在提高并发环境下,相同SQL相同参数的执行性能。
原则:只和没有缓存的情况对比,能提升并发查询性能即可。所以,

  1. 满足强一致性要求(这里暂不考虑redis访问失败的场景,这种情况另行处理)
  2. 不考虑缓存击穿等问题,缓存原则是能缓存就缓存,可能导致缓存不一致的就丢弃;

方案:

  1. 对SQL进行解析,识别查询类SQL涉及的所有TABLE;
  2. 以SQL及其参数序列化作为缓存的KEY,将查询结果的二维表序列化缓存(包含列名、列类型);
  3. 缓存数据以hash结构保存在redis中,除了保存二维表数据外,另外保存一个版本号信息(自增的整数);
  4. 事务执行过程中,SELECT语句产生的缓存,先暂存在本地,Update/Delete语句涉及的Table也缓存在本地;
  5. 事务如果回滚(显示回滚、或者连接被关闭),则丢弃本地的缓存;反之在事务提交时,将本地缓存刷新到redis中;
  6. 从缓存中查询时,如果命中则直接可用;否则,记录当前缓存版本号,查询DB,事务提交时更新redis(在更新时检查版本号是否一致、同时版本号+1;如果版本号不一致,则丢弃本次更新内容)
  7. 事务提交时,根据需要清理缓存的TABLE,找到所有的缓存SQL,将其value置为null,同时版本号+1;

感觉类似这种方式处理,可以比较简单实现强一致性缓存(访问redis失败的情况另行考虑)。
就是 使用版本号来实现类似乐观锁定的方式,防止DB和缓存不一致。
版本号限制了,所有的产生写缓存的操作(其相关的SQL查询)一定会发生在数据库事务提交之后、且删除缓存之后。这样延迟双删要解决的不一致场景就不存在了。

另外,hibernate的二级缓存是保证集群之间的缓存一致性呢?是否存在同样的问题呢?有时间再看一下!

阅读 1.2k
1 个回答

你已经提出了缓存延时双删策略了,其实不存在同步redis,会直接删除redis,当然还是会有一定几率出现,但是当引入缓存机制的系统,就默认接受一定的容错,如果你的系统这个模块就需要高精度的强一致性,建议还是在mysql直接实现,不要引入缓存策略

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题