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

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

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

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


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