如何解决并发环境下双写不一致的问题?

简介: 在并发环境下,“双写不一致”指数据库与缓存因操作顺序或执行时机差异导致数据不匹配。解决核心是保证操作的原子性、顺序性或最终一致性。常见方案包括延迟双删、加锁机制、binlog同步、版本号机制和读写锁分离,分别适用于不同一致性要求和并发场景,需根据业务需求综合选择。

在并发环境下,“双写不一致”指的是多个线程同时更新数据库和缓存时,因操作顺序或执行时机差异导致缓存与数据库数据不匹配的问题。解决这一问题的核心是保证数据库与缓存操作的原子性、顺序性或最终一致性,具体方案需结合业务对一致性的要求(强一致性/最终一致性)和性能需求选择。

一、核心解决方案

1. 延迟双删策略(最终一致性,适合高并发场景)

在“先删除缓存,再更新数据库”的基础上,增加第二次删除缓存的延迟操作,解决并发读写导致的不一致。

  • 步骤
    1. 线程A删除缓存;
    2. 线程A更新数据库;
    3. 延迟一段时间(如几百毫秒)后,线程A再次删除缓存。
  • 原理
    延迟删除的目的是覆盖“数据库更新期间,其他线程读取旧数据并写入缓存”的场景。例如:
    • 线程A删除缓存→更新数据库(耗时100ms);
    • 线程B在这100ms内读取数据,发现缓存为空,从数据库读取旧值并写入缓存;
    • 线程A更新数据库后,延迟100ms再次删除缓存,此时线程B写入的旧值会被删除,后续请求会从数据库加载新值到缓存,保证最终一致。
  • 注意
    • 延迟时间需大于业务中“读取+写入缓存”的最大耗时(可通过压测确定);
    • 可通过消息队列或定时任务执行延迟删除,避免阻塞主线程。

2. 加锁机制(强一致性,适合低并发、高一致性场景)

通过分布式锁或本地锁,保证同一时间只有一个线程操作数据,避免并发冲突。

  • 分布式锁方案(适合分布式系统):
    1. 线程获取分布式锁(如基于Redis的SET NX命令);
    2. 持有锁的线程执行“更新数据库→删除缓存”(或“删除缓存→更新数据库”);
    3. 操作完成后释放锁,其他线程需等待锁释放后再执行。
  • 本地锁方案(适合单节点服务):
    synchronizedReentrantLock锁住数据更新逻辑,确保单JVM内操作串行化。
  • 优点:严格保证一致性,避免并发冲突;
  • 缺点:降低并发性能(锁竞争会阻塞线程),需注意锁超时和死锁问题。

3. 数据库binlog同步(最终一致性,适合高可用场景)

通过监听数据库的binlog日志,异步同步数据到缓存,彻底解耦数据库与缓存的更新逻辑。

  • 步骤
    1. 线程仅更新数据库(不操作缓存);
    2. 数据库更新后,binlog日志记录变更(如MySQL的binlog);
    3. 通过中间件(如Canal)监听binlog,解析出数据变更后,异步更新或删除缓存。
  • 原理:以数据库为“唯一数据源”,缓存的更新完全依赖数据库的变更日志,避免双写操作的时序问题。
  • 优点
    • 无锁竞争,不影响业务接口性能;
    • 即使缓存更新失败,可通过重试机制(如消息队列重试)保证最终一致;
  • 缺点
    • 存在一定延迟(binlog同步→缓存更新的耗时),不适合强实时性场景;
    • 需部署binlog监听组件,增加系统复杂度。

4. 版本号机制(最终一致性,适合有序更新场景)

为数据增加版本号字段,通过版本号判断缓存是否需要更新,避免旧数据覆盖新数据。

  • 步骤
    1. 数据库表中新增version字段(初始为0,每次更新+1);
    2. 线程更新数据库时,同时更新版本号(如UPDATE ... SET version = version + 1 WHERE id = ? AND version = ?);
    3. 缓存中存储数据时,同时保存版本号;
    4. 读取缓存时,若缓存版本号低于数据库最新版本号,则忽略缓存,从数据库加载新数据并更新缓存。
  • 原理:通过版本号确保“只有最新版本的数据能写入缓存”,避免并发更新时旧数据覆盖新数据。
  • 适用场景:数据更新有明确顺序(如商品库存、用户积分),且允许短暂的版本差。

5. 读写锁分离(降低冲突,适合读多写少场景)

通过读写锁控制“读缓存”和“写缓存+数据库”的并发关系,减少冲突概率。

  • 规则
    • 写操作(更新数据库+缓存):获取写锁(排他锁),此时其他读写操作需等待;
    • 读操作(读取缓存/数据库):获取读锁(共享锁),多个读操作可并行,但需等待写锁释放。
  • 原理:保证写操作的原子性,避免读操作在写操作执行过程中加载旧数据到缓存。
  • 注意:需使用分布式读写锁(如Redisson的RReadWriteLock),适合分布式系统。

二、方案对比与选择建议

方案 一致性级别 性能 复杂度 适用场景
延迟双删 最终一致性 高并发、允许短暂不一致(如商品详情)
分布式锁 强一致性 中(有锁竞争) 低并发、强一致性(如订单状态)
binlog同步 最终一致性 高可用、读写分离架构(如用户信息)
版本号机制 最终一致性 有序更新场景(如库存、积分)
读写锁分离 最终一致性 读多写少、需减少冲突(如新闻资讯)

三、总结

解决并发双写不一致的核心思路是:

  1. 强一致性需求:优先选择分布式锁读写锁,通过串行化操作避免冲突;
  2. 高并发+最终一致性:优先选择延迟双删binlog同步,在性能与一致性间平衡;
  3. 复杂业务场景:可组合多种方案(如“版本号+延迟双删”),进一步降低不一致概率。

需注意,没有“万能方案”,需根据业务对一致性的敏感度、并发量、系统复杂度等因素综合选择,避免过度设计。

目录
相关文章
|
SQL 关系型数据库 数据库
学习分布式事务Seata看这一篇就够了,建议收藏
学习分布式事务Seata看这一篇就够了,建议收藏
26088 2
|
canal 缓存 NoSQL
Redis缓存与数据库如何保证一致性?同步删除+延时双删+异步监听+多重保障方案
根据对一致性的要求程度,提出多种解决方案:同步删除、同步删除+可靠消息、延时双删、异步监听+可靠消息、多重保障方案
Redis缓存与数据库如何保证一致性?同步删除+延时双删+异步监听+多重保障方案
|
4月前
|
canal 缓存 NoSQL
数据库扛不住高并发?Redis缓存+双写一致性:给你的系统装上“涡轮增压”
数据库小学妹带你破解Redis缓存一致性难题!面对高并发,如何确保Redis与数据库数据同步?详解“先更库后删缓”“延时双删”“Binlog异步同步”等4大方案,直击雪崩、击穿、穿透三座大山,助你构建又快又稳的数据库架构.
|
9月前
|
存储 机器学习/深度学习 人工智能
GEO 优化必备:RAG 技术全解析(基于知识密集型 NLP 经典论文)
2020 年论文提出的 RAG(检索增强生成),专治大模型 “幻觉、知识过时” 等落地痛点。它将 “检索外部知识” 与 “生成回答” 深度绑定,先精准抓取相关知识片段,再让模型基于证据生成内容。通过端到端联合训练,检索与生成协同优化,事实准确率显著提升,幻觉率大降。无需重训模型即可更新知识,还能追溯答案来源。如今成企业客服、医疗法律等领域刚需,推动大模型从 “通用” 走向 “可信实用”。这让我们做GEO优化就有了基础理论和方法。
GEO 优化必备:RAG 技术全解析(基于知识密集型 NLP 经典论文)
|
缓存 Java 数据库连接
mybatis复习05,mybatis的缓存机制(一级缓存和二级缓存及第三方缓存)
文章介绍了MyBatis的缓存机制,包括一级缓存和二级缓存的配置和使用,以及如何整合第三方缓存EHCache。详细解释了一级缓存的生命周期、二级缓存的开启条件和配置属性,以及如何通过ehcache.xml配置文件和logback.xml日志配置文件来实现EHCache的整合。
mybatis复习05,mybatis的缓存机制(一级缓存和二级缓存及第三方缓存)
|
消息中间件 canal 缓存
Redis与MySQL双写一致性如何保证:延迟双删?binlog异步删除?
Redis与MySQL双写一致性如何保证:延迟双删?binlog异步删除?
4445 0
|
Java
线程池的核心参数及执行原理你知道嘛?
线程池是一种管理和复用线程的机制,它可以提高线程的利用率和系统的性能。
1075 0
|
消息中间件 Java API
一文带你速通Sentinel限流规则(流控)解读
一文带你速通Sentinel限流规则(流控)解读
|
消息中间件 存储 缓存
深度解读 RocketMQ 存储机制
本文想从一个不一样的视角,着重于谈谈我眼中的这种存储实现是在解决哪些复杂的问题,因此我从本文最初的版本中删去了冗杂的代码细节分析,由浅入深的分析存储机制的缺陷与优化方向。
1195 1
深度解读 RocketMQ  存储机制
|
SQL 测试技术 数据库
`SELECT ... FOR UPDATE` 语句是如何工作的?
`SELECT ... FOR UPDATE` 语句是如何工作的?
1384 0