• 数据更新策略:到底是先更新数据库还是先更新缓存?

数据更新策略:到底是先更新数据库还是先更新缓存?

2025-06-12 10:00:03 栏目:宝塔面板 9 阅读

很多小伙伴最近都在问我,在系统中引入缓存后,当向数据库中写入数据时,是先写数据库还是先写缓存呢?先写数据库和先写缓存有什么区别吗?今天,我们就一起来聊聊这个话题。


又一个可直接应用于生产环境的熔断组件项目完结并上线,点击链接:https://t.zsxq.com/HIE6n 快速学习,并可直接应用于你的生产环境项目。

从本质上讲,无论是先写数据库还是先写缓存,都是为了保证数据库和缓存的数据一致,也就是我们常说的数据一致性。

随着互联网的高速发展,当今时代已然从IT时代进入到DT时代。互联网系统架构也已经由最初的单体架构转变为分布式、微服务架构模式。从数据体量上来看,各系统存储的数据量越来越大,数据的查询性能越来越低。此时,就需要我们不断的进行优化,一种常用的优化手段就是引入缓存。而引入缓存后,我们在向数据库插入数据时,到底是先更新数据库还是先更新缓存呢?

缓存的一般使用

缓存,从本质上讲,是为了更好的协调两个速度差异比较大的组件而引入的一种中间缓存层。例如,如果需要将数据读入CPU进行计算处理,由于CPU的运算速度是非常快的,而磁盘的IO处理相比于CPU来说,慢了很多数量级,每次从磁盘读取数据,势必会造成CPU长时间并且频繁等待磁盘IO。此时,我们就可以通过内存来缓和CPU和磁盘之间的速度差异。

图片

从缓存的使用上来说,一般是按照如下的流程来使用缓存。

图片

我们也可以表示成如下的序列图。

图片

在上面的使用示例中,我们只是简单的将数据放入了缓存,最多为缓存设置一个过期时间,到期后,缓存自然就会被清除,后续的请求由于在缓存中获取不到数据,又会从数据库中获取数据,将数据写入缓存。

但是在后续更新数据的操作中,是更新完数据库,接下来更新缓存还是删除缓存?又或者是先删除缓存,再更新数据库?

缓存更新策略

从理论上来说,给缓存设置过期时间,其实是一种最终一致性的表现。这种方案下,可以对存入缓存的数据设置过期时间,所有的写操作以数据库为准,对缓存操作只是尽最大努力即可。也就是说如果数据库写成功,缓存更新失败,那么只要到达过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存。这也是一般情况下,使用的最多的一种方式。

先更新数据库再更新缓存

其实,这种方案很多有经验的小伙伴是很反对的,为啥,我们来分析下。

首先,这种方案会有线程安全的问题。

例如,同时有线程A和线程B对数据进行更新操作,可能会出现下面的执行顺序。

(1) 线程A更新了数据库

(2) 线程B更新了数据库

(3) 线程B更新了缓存

(4) 线程A更新了缓存

此时就会出现数据库中的数据与缓存的数据不一致的情况,这是因为线程A先更新了数据库,可能因为网络等异常情况,线程B更新完数据库进而更新了缓存,当线程B更新完缓存后,线程A才更新缓存,这就导致了数据库数据与缓存数据的不一致。

其次,这种方案也有其不适用的业务场景。

首先一个业务场景就是数据库写多读少的场景,这种场景下采用先更新数据库再更新缓存的策略,就会导致缓存并未被读取就会被频繁的更新,极大的浪费了服务器的性能。

再一个业务场景就是数据库中的数据不是直接写入缓存的,而是需要大量的复杂运算,将运算结果写入缓存。如果这种场景下使用先更新数据库再更新缓存的策略,也会造成服务器资源的浪费。

先删除缓存再更新数据库

先删除缓存再更新数据库的方案也存在着线程安全的问题,例如,线程A更新缓存,同时,线程B读取缓存的数据。可能会出现下面的执行顺序。

(1) 线程A删除缓存

(2) 线程B查询缓存,发现缓存中没有想要的数据

(3) 线程B查询数据库中的旧数据

(4) 线程B将查询到的旧数据写入缓存

(5) 线程A将新数据写入数据库

此时,就出现了数据库中的数据和缓存中的数据不一致的情况。如果删除缓存失败,也会出现数据库数据和缓存数据不一致的现象。

先更新数据库再删除缓存

首先,这种方式也有极小的概率发生数据库数据和缓存数据不一致的情况,例如,线程A做查询操作,线程B执行更新操作,其执行的顺序如下所示。

(1)缓存刚好失效

(2)请求A查询数据库,获取到数据库中的旧值

(3)请求B将新值写入数据库

(4)请求B删除缓存

(5)请求A将查到的旧值写入缓存

如果上述顺序一旦发生,就会造成数据库中的数据和缓存中的数据不一致的情况发生。

但是,先更新数据库再删除缓存的策略发生数据库和缓存数据不一致的概率很低,原因就是:(3)的写数据库操作比步骤(2)的读数据库操作耗时更短,才有可能使得步骤(4)先于步骤(5)执行。但是,往往数据库的读操作的速度远快于写操作,因此步骤(3)耗时比步骤(2)更短,这一场景很难出现。

如果删除缓存失败,也会出现数据库数据和缓存数据不一致的现象。

这样说来,貌似三种方案都不安全呀,那我们该如何做呢?最重要的就是需要引入重试机制。

推荐使用

在实际的生产环境中,推荐 使用先更新数据库再删除缓存 的操作。那么,我们该如何解决这种策略下的问题呢?

有两种方案,一种是在程序逻辑中处理失败重试的操作;另外,借助于阿里巴巴开源的Canal。

手动失败重试

图片

流程如下所示

(1)更新数据库数据;

(2)删除缓存数据失败

(3)将需要删除的key发送至消息队列

(4)自己消费消息,获得需要删除的key

(5)继续重试删除操作,直到成功

这种方案有一个缺点,对业务线代码造成大量的侵入。

同步数据库数据

先来一张图,这种图从整体架构上解决了数据库数据和缓存数据不一致的情况。

图片

流程如下图所示:

(1)更新数据库数据

(2)数据库将数据表数据的变更信息写入binlog日志当中

(3)订阅程序获取所需要的数据以及key

(4)程序逻辑中处理具体的业务逻辑,接收订阅binlog、发起删除缓存的请求。

(5)尝试删除缓存操作,发现删除失败

(6)将这些信息发送至消息队列

(7)重新从消息队列中获得该数据,重试操作。


本文地址:https://www.yitenyun.com/283.html

搜索文章

Tags

数据库 API FastAPI Calcite 电商系统 MySQL 数据同步 ACK Web 应用 异步数据库 双主架构 循环复制 序列 核心机制 生命周期 Deepseek 宝塔面板 Linux宝塔 Docker JumpServer JumpServer安装 堡垒机安装 Linux安装JumpServer esxi esxi6 root密码不对 无法登录 web无法登录 Windows Windows server net3.5 .NET 安装出错 宝塔面板打不开 宝塔面板无法访问 SSL 堡垒机 跳板机 HTTPS Windows宝塔 Mysql重置密码 无法访问宝塔面板 HTTPS加密 查看硬件 Linux查看硬件 Linux查看CPU Linux查看内存 连接控制 机制 ES 协同 修改DNS Centos7如何修改DNS scp Linux的scp怎么用 scp上传 scp下载 scp命令 Serverless 无服务器 语言 Oracle 处理机制 存储 防火墙 服务器 黑客 Spring SQL 动态查询 RocketMQ 长轮询 配置 Linux 安全 加密 场景 Rsync MySQL 9.3 缓存方案 缓存架构 缓存穿透 日志文件 MIXED 3 网络架构 工具 网络配置 HexHub Canal 开源 PostgreSQL 存储引擎 架构 InnoDB 线上 库存 预扣 Redis Redis 8.0 索引 数据 业务 数据库锁 信息化 智能运维 响应模型 聚簇 非聚簇 B+Tree ID 字段 自定义序列化 分页查询 监控 prometheus Alert 单点故障 云原生 openHalo AI 助手 GreatSQL Hash 字段 分库 分表 技术 排行榜 排序 查询 ​Redis 机器学习 推荐模型 容器化 DBMS 管理系统 OB 单机版 Doris SeaTunnel 自动重启 运维 共享锁 SpringAI 优化 万能公式 数据集成工具 向量数据库 大模型 Postgres OTel Iceberg 电商 系统 SVM Embedding PostGIS SQLark Entity 开发 Netstat Linux 服务器 端口 人工智能 推荐系统 SQLite Redka SQLite-Web 数据库管理工具 不宕机 sqlmock sftp 服务器 参数 虚拟服务器 虚拟机 内存 • 索引 • 数据库 RDB AOF redo log 重做日志 EasyExcel MySQL8 同城 双活 数据备份 分布式架构 分布式锁​ 聚簇索引 非聚簇索引 崖山 新版本 高可用 OAuth2 Token Testcloud 云端自动化 分页 数据结构 缓存 MongoDB 容器 向量库 Milvus StarRocks 数据仓库 IT运维 Ftp IT AIOPS 数据类型 MVCC 池化技术 连接池 Python Web 数据脱敏 加密算法 LRU mini-redis INCR指令 ZODB 悲观锁 乐观锁 Caffeine CP 磁盘架构 流量 MCP 开放协议 Web 接口 窗口 函数 部署 微软 SQL Server AI功能 字典 单线程 线程 事务隔离 原子性 1 PG DBA 速度 服务器中毒 R2DBC QPS 高并发 RAG HelixDB 对象 Order 网络 双引擎 主库 工具链 引擎 SSH 性能 Pottery dbt 数据转换工具 InfluxDB 频繁 Codis 模型 Undo Log 优化器 List 类型 LLM 连接数 网络故障 Crash 代码 INSERT COMPACT 事务同步 JOIN 管理口 发件箱模式 意向锁 记录锁 Recursive Redisson 锁芯 线程安全 传统数据库 向量化 高效统计 今天这篇文章就跟大家 filelock UUIDv7 主键 Go 数据库迁移 仪表盘 订单 分页方案 排版 Pump 核心架构 订阅机制 大表 业务场景 启动故障 分布式 集中式