【MySQL飞升篇】面试必问:MySQL与Redis缓存一致性,看这篇就够了
予枫个人主页 个人专栏: 《Java 从入门到起飞》《读研码农的干货日常》 Debug 这个世界Return 更好的自己引言做后端开发的同学几乎都逃不开MySQL与Redis的组合使用——用Redis做缓存减轻数据库压力却常被缓存与数据库数据不一致的问题搞得头大读缓存命中脏数据、写操作后缓存同步不及时、并发场景下数据错乱… 这篇文章就聚焦两个经典解决方案Cache Aside Pattern旁路缓存模式和延迟双删策略从原理拆解到实战落地帮你彻底搞定缓存一致性难题面试和工作都能用文章目录引言一、缓存一致性核心问题剖析1.1 常见不一致场景1.2 核心诉求最终一致性二、CACHE ASIDE PATTERN旁路缓存模式2.1 核心原理读/写分离2.1.1 读操作流程3步走2.1.2 写操作流程3步走2.2 适用场景与优势适用场景核心优势2.3 存在的问题三、延迟双删策略解决更新场景的一致性难题3.1 核心原理4步走3.2 关键解析为什么要“双删延迟”第一次删缓存延迟一段时间第二次删缓存3.3 适用场景与优势适用场景核心优势3.4 关键注意事项四、实战代码示例JavaSpring Boot4.1 环境依赖pom.xml核心依赖4.2 Cache Aside Pattern实现商品查询更新4.3 延迟双删策略优化基于RabbitMQ延迟队列4.3.1 延迟队列配置4.3.2 延迟双删实现优化写操作五、常见问题与避坑指南5.1 缓存删除失败怎么办5.2 如何避免缓存穿透5.3 高并发场景下的极致优化六、总结一、缓存一致性核心问题剖析在聊解决方案前我们得先搞懂为什么MySQL和Redis会出现数据不一致核心原因就3点尤其是并发场景下更突出核心矛盾缓存与数据库是两个独立存储写操作无法同时原子性完成读操作可能读取到未同步的旧数据。1.1 常见不一致场景更新场景先更数据库再删缓存删缓存失败导致缓存存旧数据先删缓存再更数据库更新期间读请求缓存未命中查数据库旧数据写入缓存造成脏数据。并发场景一个写操作删缓存更数据库和一个读操作同时执行读操作在写操作更新数据库前读取旧数据并写入缓存。缓存过期/失效缓存过期后大量请求穿透到数据库同时有写操作导致部分请求读取到中间态数据。1.2 核心诉求最终一致性这里要明确分布式系统中很难做到强一致性成本极高我们追求的是最终一致性——即经过短暂时间后缓存数据与数据库数据完全同步满足业务需求。 小提示如果你的业务对数据一致性要求极高如金融交易可能需要引入分布式锁等更复杂的机制本文聚焦大多数业务场景的经典方案~二、CACHE ASIDE PATTERN旁路缓存模式Cache Aside Pattern是最经典、最常用的缓存策略核心思路是“读走缓存、写走数据库”缓存只作为“旁路”辅助不参与核心写流程。2.1 核心原理读/写分离2.1.1 读操作流程3步走客户端请求数据时先查询Redis缓存若缓存命中存在且未过期直接返回缓存数据结束流程若缓存未命中查询MySQL数据库将查询结果写入Redis缓存设置合理过期时间再返回结果给客户端。是否客户端请求数据缓存命中返回缓存数据查询MySQL数据库将结果写入Redis2.1.2 写操作流程3步走客户端发起写请求先更新MySQL数据库数据库更新成功后删除Redis缓存而非更新缓存返回写操作成功结果给客户端。❌ 为什么是“删缓存”而非“更缓存”避免更新缓存成本过高如复杂计算后的结果减少并发场景下的一致性问题更新缓存和更新数据库无法原子化下次读请求自然会从数据库加载最新数据并重建缓存。成功失败客户端写请求更新MySQL数据库删除Redis缓存返回写成功结果返回写失败结果2.2 适用场景与优势适用场景读多写少的业务如商品详情、用户信息查询对一致性要求为“最终一致”的场景缓存数据可通过数据库快速重建的场景。核心优势逻辑简单开发成本低易于落地缓存利用率高读请求优先走缓存减轻数据库压力减少缓存与数据库的同步冲突降低脏数据概率。2.3 存在的问题Cache Aside Pattern并非完美核心问题出在“写后删缓存”的时序上若更新数据库成功但删除缓存失败如Redis宕机、网络波动则缓存中仍存旧数据后续读请求会命中脏数据并发场景下“读未命中→查旧数据→写缓存”与“更数据库→删缓存”重叠可能导致缓存写入旧数据。✨ 小互动你在项目中遇到过Cache Aside Pattern的坑吗欢迎在评论区留言分享 记得点赞收藏后续持续更新架构实战技巧三、延迟双删策略解决更新场景的一致性难题延迟双删策略是对Cache Aside Pattern的优化专门解决“写操作后缓存删除失败”和“并发读写导致脏数据”的问题核心思路是“两次删除缓存中间加延迟”。3.1 核心原理4步走客户端发起写请求先删除Redis缓存更新MySQL数据库延迟一段时间如500ms、1s根据业务耗时调整再次删除Redis缓存。成功失败客户端写请求第一次删除Redis缓存更新MySQL数据库延迟N毫秒第二次删除Redis缓存返回写成功结果返回写失败结果3.2 关键解析为什么要“双删延迟”第一次删缓存目的提前清空旧缓存避免后续读请求在“数据库更新前”读取到旧缓存数据。延迟一段时间目的等待数据库更新完成后再执行第二次删除确保期间可能写入缓存的旧数据并发读导致被清除延迟时间选择需大于“数据库更新耗时 并发读请求从数据库查数据并写入缓存的耗时”一般建议500ms~3s可通过压测调整。第二次删缓存目的兜底操作即使第一次删缓存失败、或并发读请求在数据库更新后写入了旧数据第二次删除也能将脏数据清空保证后续读请求加载最新数据。3.3 适用场景与优势适用场景写操作较多、并发场景复杂的业务对缓存一致性要求较高不允许长期存在脏数据的场景已使用Cache Aside Pattern但并发问题无法解决的场景。核心优势解决了Cache Aside Pattern的并发读写脏数据问题通过二次删除兜底降低缓存删除失败导致的不一致风险兼容性强可在原有Cache Aside架构上快速改造。3.4 关键注意事项延迟时间的合理性太短无法覆盖数据库更新和缓存写入耗时太长会导致短暂的缓存空窗期读请求频繁穿透数据库第二次删缓存的可靠性建议通过消息队列如RabbitMQ、RocketMQ实现延迟删除避免服务宕机导致第二次删除失败数据库事务一致性更新数据库需保证事务完整性避免数据库更新一半失败导致缓存已删、数据未更的情况。 实操建议延迟双删的延迟时间可通过模拟并发场景压测得出比如统计1000次并发读写的平均耗时在此基础上增加20%的冗余时间。四、实战代码示例JavaSpring Boot下面以“商品信息管理”为例基于Spring BootMySQLRedis实现Cache Aside Pattern和延迟双删策略的实战代码直接抄作业4.1 环境依赖pom.xml核心依赖dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-jdbc/artifactId/dependencydependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdscoperuntime/scope/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-amqplt;/artifactIdgt;!-- 用于延迟双删的消息队列 --/dependency4.2 Cache Aside Pattern实现商品查询更新ServicepublicclassProductServiceImplimplementsProductService{AutowiredprivateProductMapperproductMapper;// MyBatis Mapper接口AutowiredprivateStringRedisTemplateredisTemplate;// 缓存key前缀privatestaticfinalStringCACHE_KEY_PRODUCTproduct:info:;// 读操作Cache Aside PatternOverridepublicProductgetProductById(Longid){// 1. 查询缓存StringkeyCACHE_KEY_PRODUCTid;StringproductJsonredisTemplate.opsForValue().get(key);if(StrUtil.isNotBlank(productJson)){// 缓存命中返回数据returnJSONUtil.toBean(productJson,Product.class);}// 2. 缓存未命中查询数据库ProductproductproductMapper.selectById(id);if(productnull){// 数据库无数据可设置空缓存避免缓存穿透redisTemplate.opsForValue().set(key,,5,TimeUnit.MINUTES);returnnull;}// 3. 将数据库数据写入缓存设置1小时过期redisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(product),1,TimeUnit.HOURS);returnproduct;}// 写操作Cache Aside Pattern写后删缓存OverrideTransactional// 保证数据库事务一致性publicBooleanupdateProduct(Productproduct){// 1. 更新数据库booleanupdateSuccessproductMapper.updateById(product)0;if(!updateSuccess){returnfalse;}// 2. 删除缓存StringkeyCACHE_KEY_PRODUCTproduct.getId();redisTemplate.delete(key);returntrue;}}4.3 延迟双删策略优化基于RabbitMQ延迟队列4.3.1 延迟队列配置ConfigurationpublicclassRabbitMQDelayConfig{// 延迟交换机publicstaticfinalStringDELAY_EXCHANGEdelay_exchange;// 延迟队列删除缓存publicstaticfinalStringDELAY_QUEUE_CACHE_DELETEdelay_queue_cache_delete;// 路由键publicstaticfinalStringDELAY_ROUTING_KEYdelay_routing_key;// 延迟交换机基于死信交换机实现BeanpublicDirectExchangedelayExchange(){returnnewDirectExchange(DELAY_EXCHANGE);}// 延迟队列BeanpublicQueuedelayCacheDeleteQueue(){MapString,ObjectargsnewHashMap();// 设置死信交换机与延迟交换机同名简化配置args.put(x-dead-letter-exchange,DELAY_EXCHANGE);// 设置死信路由键args.put(x-dead-letter-routing-key,DELAY_ROUTING_KEY);returnQueueBuilder.durable(DELAY_QUEUE_CACHE_DELETE).withArguments(args).build();}// 队列绑定BeanpublicBindingdelayCacheDeleteBinding(){returnBindingBuilder.bind(delayCacheDeleteQueue()).to(delayExchange()).with(DELAY_ROUTING_KEY);}}4.3.2 延迟双删实现优化写操作ServicepublicclassProductServiceImplimplementsProductService{AutowiredprivateProductMapperproductMapper;AutowiredprivateStringRedisTemplateredisTemplate;AutowiredprivateRabbitTemplaterabbitTemplate;privatestaticfinalStringCACHE_KEY_PRODUCTproduct:info:;// 写操作延迟双删策略OverrideTransactionalpublicBooleanupdateProductWithDelayDoubleDelete(Productproduct){StringkeyCACHE_KEY_PRODUCTproduct.getId();// 1. 第一次删除缓存redisTemplate.delete(key);// 2. 更新数据库booleanupdateSuccessproductMapper.updateById(product)0;if(!updateSuccess){returnfalse;}// 3. 发送延迟消息执行第二次删除延迟1秒rabbitTemplate.convertAndSend(RabbitMQDelayConfig.DELAY_EXCHANGE,RabbitMQDelayConfig.DELAY_ROUTING_KEY,key,message-{// 设置消息过期时间1秒message.getMessageProperties().setExpiration(1000);returnmessage;});returntrue;}// 延迟消息消费者执行第二次缓存删除RabbitListener(queuesRabbitMQDelayConfig.DELAY_QUEUE_CACHE_DELETE)publicvoidhandleDelayCacheDelete(Stringkey){try{// 第二次删除缓存redisTemplate.delete(key);log.info(延迟双删第二次删除缓存成功key:{},key);}catch(Exceptione){log.error(延迟双删第二次删除缓存失败key:{},key,e);// 可加入重试机制如定时任务兜底}}// 读操作与上文Cache Aside Pattern一致省略...}五、常见问题与避坑指南5.1 缓存删除失败怎么办方案1引入消息队列重试机制如上述延迟双删中的RabbitMQ失败后重试3次方案2定时任务兜底定期对比数据库与缓存数据清理脏数据方案3使用Redis的持久化机制AOFRDB确保Redis重启后缓存数据可恢复减少删除失败影响。5.2 如何避免缓存穿透缓存空值数据库查询无结果时写入空缓存设置较短过期时间布隆过滤器在缓存前增加布隆过滤器过滤不存在的key避免无效数据库查询。5.3 高并发场景下的极致优化加分布式锁在读未命中→写缓存的流程中加锁如Redisson分布式锁避免同一key并发写入旧数据缓存预热系统启动时将热点数据提前加载到缓存减少缓存未命中场景缓存降级流量峰值时关闭非核心业务的缓存更新优先保证读请求可用性。✨ 面试小贴士被问到MySQL与Redis缓存一致性时先答“最终一致性”的核心诉求再讲Cache Aside Pattern的基础流程最后补充延迟双删的优化方案和避坑点轻松拿下面试分六、总结本文围绕MySQL与Redis缓存一致性问题详细拆解了两个经典解决方案Cache Aside Pattern读走缓存、写走数据库逻辑简单易落地适合读多写少场景延迟双删策略通过“双删延迟”优化解决并发读写脏数据和缓存删除失败问题适合一致性要求较高的场景。核心原则分布式系统中不追求强一致性而是通过合理的策略实现最终一致性同时结合业务场景选择合适的方案必要时引入分布式锁、消息队列等工具兜底。

相关新闻

[信息论与编码理论专题-16]:等概率时熵最大、编码最长;实际概率不均,熵降低,变长编码可压缩,平均码长更短。

[信息论与编码理论专题-16]:等概率时熵最大、编码最长;实际概率不均,熵降低,变长编码可压缩,平均码长更短。

在一个包含 N 个可能事件的系统中,当所有事件等概率发生时,系统的熵达到最大值 log 2​N ,此时对事件进行最优无损编码所需的平均码长也达到理论最大值。 而在实际系统中,事件发生的概率往往不相等;若存在较多高概率事…

2026/7/3 4:44:45 阅读更多 →
DeepSeek总结DuckPL:为DuckDB引入过程式编程语言

DeepSeek总结DuckPL:为DuckDB引入过程式编程语言

DuckPL:为DuckDB引入过程式编程语言 原文地址:https://blobs.duckdb.org/events/duckdb-developer-meeting-1/duckpl-a-procedural-language-in-duckdb-denis-hirn.pdf 在2026年1月30日的DuckDB开发者会议#1上,来自蒂宾根大学的Denis Hirn&…

2026/7/3 14:44:34 阅读更多 →
【计算机毕业设计案例】基于VUE框架的实时新闻推送平台新闻订阅平台(程序+文档+讲解+定制)

【计算机毕业设计案例】基于VUE框架的实时新闻推送平台新闻订阅平台(程序+文档+讲解+定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/7/3 14:44:35 阅读更多 →

最新新闻

Qt/QML音视频文件原始十六进制查看器

Qt/QML音视频文件原始十六进制查看器

前言 在做音视频工具时,很多问题只看 FFmpeg 解析后的字段并不够。比如: MP4 的 ftyp、moov、mdat 到底在文件哪个位置;WAV/AVI 的 RIFF、fmt 、data 块大小是否正确;某段元数据、魔数或 ASCII 字符串是否真的存在于原始文件里&am…

2026/7/4 4:22:09 阅读更多 →
【安心陪诊 Agent】从 Web Demo 到 HAP 真机:安心陪诊 Agent 的工程落地路线

【安心陪诊 Agent】从 Web Demo 到 HAP 真机:安心陪诊 Agent 的工程落地路线

应用名称:安心陪诊 Agent 统一合集:安心陪诊 Agent|HarmonyOS 高校创新赛 关键词标签:harmonyos / AI Agent / 医疗陪诊从 Web Demo 到 HAP 真机:安心陪诊 Agent 的工程落地路线摘要:规划从当前 Web 原型到…

2026/7/4 4:22:09 阅读更多 →
查询服务器RAID卡-lspci命令

查询服务器RAID卡-lspci命令

说明 老服务器使用sas卡,需要lspci 工具查询 安装工具 yum install -y pciutils查询RAID卡型号 lspci | grep -i "raid\|sas"03:00.0 RAID bus controller: Broadcom / LSI MegaRAID SAS 2208 [Thunderbolt] (rev 05)

2026/7/4 4:20:09 阅读更多 →
AI 工具开发实战(2):开发一个本地 RAG 知识库——丢一个文件夹进去,直接问答

AI 工具开发实战(2):开发一个本地 RAG 知识库——丢一个文件夹进去,直接问答

AI 工具开发实战(2):开发一个本地 RAG 知识库——丢一个文件夹进去,直接问答 上一篇做了一个命令行翻译工具,这篇做一个更实用的:本地 RAG 知识库。 把 PDF、Markdown、TXT 文件丢到一个文件夹里&#xf…

2026/7/4 4:18:08 阅读更多 →
基于CNN卷积神经网络手写汉字识别系统 (GUI界面)【源码38期】

基于CNN卷积神经网络手写汉字识别系统 (GUI界面)【源码38期】

一、项目简介本系统基于MATLAB深度学习工具箱,设计并实现了一个基于卷积神经网络(CNN)的手写汉字识别系统。系统包含三大核心模块:网络结构定义模块(get_self_net.m)封装了CNN网络构建函数,采用…

2026/7/4 4:16:08 阅读更多 →
YLB3118@ACP#国产8口SATA3.0存储芯片|物理AI长时序海量数据存储国产替代旗舰(对标ASM1166)

YLB3118@ACP#国产8口SATA3.0存储芯片|物理AI长时序海量数据存储国产替代旗舰(对标ASM1166)

一、前言:物理AI时代,存储已经成为算力落地的真正瓶颈2026年物理AI全面商用落地,智源悟道4.0物理世界模型、英伟达Vera Rubin仿真算力平台、特斯拉Optimus人形机器人,彻底改写了AI数据的生产逻辑。传统生成式AI以文本、短帧图像、…

2026/7/4 4:06:03 阅读更多 →

日新闻

Memcached 1.6.43 发布:关键安全修复版本,多项问题得到解决

Memcached 1.6.43 发布:关键安全修复版本,多项问题得到解决

Memcached 1.6.43 正式发布,这是一个关键的安全修复版本,修复了多个方面的问题,还对部分功能进行了优化。 安全修复亮点 此次发布在安全修复上表现突出。binprot 避免了项目引用计数溢出,mcmc 因安全问题提升了上游版本号&#xf…

2026/7/4 0:04:29 阅读更多 →
终极指南:使用HMCL启动器跨平台畅玩Minecraft的完整解决方案

终极指南:使用HMCL启动器跨平台畅玩Minecraft的完整解决方案

终极指南:使用HMCL启动器跨平台畅玩Minecraft的完整解决方案 【免费下载链接】HMCL A Minecraft Launcher which is multi-functional, cross-platform and popular 项目地址: https://gitcode.com/gh_mirrors/hm/HMCL HMCL(Hello Minecraft! Lau…

2026/7/4 0:06:29 阅读更多 →
KMX63与PIC18F66K40在嵌入式HMI中的硬件协同与低功耗设计

KMX63与PIC18F66K40在嵌入式HMI中的硬件协同与低功耗设计

1. KMX63与PIC18F66K40的硬件协同架构解析KMX63作为一款三轴加速度计和磁力计组合传感器,与PIC18F66K40微控制器的搭配堪称嵌入式HMI开发的黄金组合。这套硬件组合的核心优势在于KMX63提供的高精度运动感知能力与PIC18F66K40强大的信号处理能力形成了完美互补。KMX6…

2026/7/4 0:06:29 阅读更多 →

周新闻

月新闻