Tavio's blog Tavio's blog
首页
  • JVM底层原理
  • 邪恶多线程
  • MyBatis底层原理
  • Spring底层原理
  • MySQL的优化之路
  • ClickHouse的高性能
  • Redis的快速查询
  • RabbitMQ的生产
  • Kafka的高吞吐量
  • ES的入门到入坑
  • MySQL自增ID主键空洞
  • 前端实现长整型排序
  • MySQL无感换表
  • Redis延时双删
  • 高并发秒杀优惠卷
  • AOP无侵入式告警
  • 长短链接跳转
  • 订单超时取消
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Tavio Zhang

努力学习的小码喽
首页
  • JVM底层原理
  • 邪恶多线程
  • MyBatis底层原理
  • Spring底层原理
  • MySQL的优化之路
  • ClickHouse的高性能
  • Redis的快速查询
  • RabbitMQ的生产
  • Kafka的高吞吐量
  • ES的入门到入坑
  • MySQL自增ID主键空洞
  • 前端实现长整型排序
  • MySQL无感换表
  • Redis延时双删
  • 高并发秒杀优惠卷
  • AOP无侵入式告警
  • 长短链接跳转
  • 订单超时取消
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • MySQL自增ID主键空洞
  • 前端实现长整型排序
  • MySQL无感换表
  • Redis延时双删
    • 一、为什么需要“延时双删”?
      • 1.1 错误场景1:先更数据库,再更缓存
      • 1.2 错误场景2:先删缓存,再更数据库(无延时)
      • 1.3 延时双删的核心价值
    • 二、延时双删核心原理与实现
      • 2.1 三步核心流程
      • 2.2 关键逻辑拆解
      • 2.3 代码落地实现
    • 三、关键参数:延时时间怎么定?
      • 推荐取值与校准方法
      • 校准技巧
    • 四、延时双删的优缺点与适用场景
      • 4.1 优点
      • 4.2 缺点
      • 4.3 适用与不适用场景
    • 五、实战避坑:这些问题一定要注意
      • 5.1 缓存删除失败怎么办?
      • 5.2 避免缓存穿透放大
      • 5.3 不要滥用延时双删
    • 六、延时双删 vs 其他一致性方案
    • 七、最佳实践总结
    • 八、总结
  • 高并发秒杀优惠卷
  • AOP无侵入式告警
  • 长短链接跳转
  • 双 Token 登录
  • 订单超时取消
  • 实践
Tavio
2022-07-11
目录

Redis延时双删

在“Redis 缓存 + MySQL 数据库”的经典架构中,数据一致性是高频痛点。延时双删作为一种实现简单、成本低廉、适配多数最终一致性场景的解决方案,被广泛应用于非核心业务(如商品详情、用户历史记录)。

# 一、为什么需要“延时双删”?

在聊延时双删前,先明确一个核心矛盾:Redis 与 MySQL 是独立存储系统,读写操作无法原子化完成,并发场景下极易出现数据不一致。我们先看两个经典错误场景,理解延时双删的诞生意义。

# 1.1 错误场景1:先更数据库,再更缓存

  • 流程:
    1. 线程A 更新 MySQL,将值从 V1→V2,数据库现在是 V2
    2. 线程B 更新 MySQL,将值从 V2→V3,数据库现在是 V3
    3. 线程B 更新 Redis,将值从 V1→V3,Redis现在是 V3
    4. 线程A 更新 Redis,将值从 V3→V2,Redis现在是 V2
  • 问题:并发修改时,MySQL 是新数据,Redis 保留旧数据,形成不一致,直到缓存过期。

# 1.2 错误场景2:先删缓存,再更数据库(无延时)

  • 流程:
    1. 线程A 删除 Redis 缓存;
    2. 线程A 未完成 MySQL 更新,事务为提交;
    3. 线程B 读取 Redis 缓存未命中,从 MySQL 读取旧数据并回写缓存;
    4. 线程A 完成 MySQL 更新。
  • 问题:Redis 被旧数据“污染”,后续请求会持续读取错误数据,直到缓存过期。

# 1.3 延时双删的核心价值

延时双删通过“两次删除缓存 + 中间延时等待”,精准解决上述并发时序问题,核心目标是让缓存数据最终收敛到与数据库一致,同时兼顾实现成本与性能。

# 二、延时双删核心原理与实现

# 2.1 三步核心流程

延时双删的操作逻辑极简,仅需三步,就能规避并发读写导致的缓存污染:

  1. 第一次删除:更新数据前,先删除 Redis 对应缓存,让后续读请求暂时穿透到数据库;
  2. 更新数据库:正常写入/更新 MySQL 数据,保证持久化存储的准确性;
  3. 延时等待后第二次删除:等待一段时间,再次删除 Redis 缓存,清除可能被线程B 回写的旧数据。

# 2.2 关键逻辑拆解

第二次删除的“延时等待”是核心,其目的是:等待所有在“第一次删缓存后、更数据库前”发起的读请求,都完成旧数据的读取与回写,再通过第二次删除清空这些旧数据。后续读请求会从数据库读取新数据,重新回写缓存,达成最终一致。

# 2.3 代码落地实现

// 延时双删核心方法(更新数据场景)
public void updateData(Long id, String newData) {
    // 缓存键(按业务设计,如前缀+ID)
    String cacheKey = "data:" + id;

    // 第一步:第一次删除缓存
    redisTemplate.delete(cacheKey);

    try {
        // 第二步:更新 MySQL 数据库(实际业务中需加事务)
        updateMySQL(id, newData);
    } catch (Exception e) {
        // 数据库更新失败,可回滚缓存(可选,视业务容错性)
        redisTemplate.opsForValue().set(cacheKey, getOldDataFromMySQL(id), 30, TimeUnit.MINUTES);
        throw new RuntimeException("数据库更新失败", e);
    }

    // 第三步:延时 1 秒后,第二次删除缓存
    executorService.schedule(() -> {
        redisTemplate.delete(cacheKey);
    }, 1, TimeUnit.SECONDS);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 三、关键参数:延时时间怎么定?

延时时间的设定直接影响一致性效果与性能,不能凭感觉定,需结合三个核心因素:

  1. 数据库事务耗时:若更新操作涉及复杂事务(如多表关联),需预留足够时间让事务提交完成;
  2. 业务 QPS 与并发量:高并发场景下,读请求回写缓存的速度更快,可适当缩短延时;低并发场景可延长,避免过早删除;
  3. 网络延迟:Redis 与 MySQL 部署在不同节点时,需考虑网络传输耗时。

# 推荐取值与校准方法

  • 常规场景:默认 1-3 秒,覆盖大部分中小并发业务;
  • 高并发场景:0.5-1 秒,减少缓存穿透时间,降低数据库压力;
  • 复杂事务场景:3-5 秒,确保事务完全提交后再二次删除。

# 校准技巧

上线后通过日志监控“第一次删缓存到第二次删缓存”期间的读请求量,若仍存在缓存污染,逐步增加延时时间(每次加 500ms),直到不一致问题消失。

# 四、延时双删的优缺点与适用场景

# 4.1 优点

  1. 实现简单:无需引入 Canal、消息队列等中间件,代码侵入性极低;
  2. 成本低廉:仅增加两次缓存删除操作,对性能影响可忽略;
  3. 容错性强:即使第二次删除失败,缓存设置了过期时间,也能兜底保证最终一致。

# 4.2 缺点

  1. 存在短时间不一致窗口:从第一次删缓存到第二次删缓存的间隙,仍可能读取到旧数据,不适用于超强一致性场景(如支付、库存);
  2. 延时时间难精准把控:不同业务场景下需反复校准,无统一标准;
  3. 高并发写场景有局限:频繁删除缓存会导致大量读请求穿透到数据库,可能引发数据库压力飙升。

# 4.3 适用与不适用场景

场景类型 是否适用 原因
商品详情、用户资料 适用 允许秒级不一致,追求高读性能
订单列表、历史记录 适用 数据变更频率低,最终一致即可
支付金额、库存数量 不适用 需强一致性,避免超卖、资金误差

# 五、实战避坑:这些问题一定要注意

# 5.1 缓存删除失败怎么办?

两次删除操作都可能因 Redis 宕机、网络超时失败,需做好兜底:

  • 第一次删除失败:直接放弃更新操作,返回错误提示,避免“数据库新数据、缓存旧数据”;
  • 第二次删除失败:借助异步重试机制(如消息队列),或依赖缓存过期时间兜底,同时监控删除失败率,超过阈值告警。

# 5.2 避免缓存穿透放大

延时双删会导致短时间内缓存穿透(第一次删后到第二次删前,读请求直接透库),高并发下需配合防护:

  • 非核心数据:设置空值缓存(如缓存不存在的数据为“null”,过期时间 5-10 秒);
  • 核心热点数据:结合互斥锁,同一时间仅允许一个线程读库回写缓存,避免数据库雪崩。

# 5.3 不要滥用延时双删

并非所有缓存更新场景都需要延时双删:

  • 写少读多场景:优先用“先删缓存+缓存过期”,可省略第二次删除,减少复杂度;
  • 数据变更频率极高场景:建议改用 Canal 异步同步方案,避免频繁删除缓存。

# 六、延时双删 vs 其他一致性方案

为帮你精准选型,对比主流缓存一致性方案的差异:

方案 一致性级别 实现复杂度 性能 适用场景
延时双删 最终一致性 低 高 中小并发、非核心业务
分布式锁+同步更新 强一致性 中 中 核心业务(库存、支付)
Canal+Binlog 异步同步 最终一致性 高 高 高并发、海量数据场景
先更库再更缓存 最终一致性 低 中 低并发、缓存更新成功率 100% 场景

# 七、最佳实践总结

  1. 必加缓存过期时间:无论延时双删多稳定,都要给缓存设置过期时间(5-30 分钟),作为不一致的最后兜底;
  2. 延时时间动态校准:上线后根据业务并发量、事务耗时调整延时时间,避免一刀切;
  3. 失败重试兜底:第二次删除失败时,通过消息队列异步重试,确保删除操作最终执行;
  4. 结合业务场景选型:非核心最终一致性场景用延时双删,核心强一致场景用分布式锁,高并发场景用 Canal 方案。

# 八、总结

Redis 延时双删是“性价比极高”的缓存一致性解决方案,核心优势在于简单落地、低侵入、高性能,完美适配多数非核心业务的最终一致性需求。其本质是通过“两次删除+延时等待”,巧妙规避并发读写的时序问题,同时借助缓存过期时间兜底,平衡一致性与性能。

编辑 (opens new window)
#Redis延时双删
上次更新: 2026/01/21, 19:29:14
MySQL无感换表
高并发秒杀优惠卷

← MySQL无感换表 高并发秒杀优惠卷→

最近更新
01
订单超时取消
01-21
02
双 Token 登录
01-21
03
长短链接跳转
01-21
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式