You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

缓存删除与刷新的数据竞争问题:如何避免脏缓存?

缓存并发更新脏数据问题的解决方案及缓存失效实践

问题本质

你碰到的是Cache-Aside(懒加载)模式下的典型缓存与数据库一致性并发冲突:GET请求缓存未命中后去查询DB,同时DELETE请求完成DB删除和缓存清理,但GET请求后续将已删除的旧数据写回缓存,导致后续请求拿到脏数据。

不止互斥锁:多种优化方案

1. 缓存版本号校验(推荐)

给Car结构体增加version字段,每次DB操作(新增/删除/更新)时同步更新版本号:

  • 优化后的getCar流程:
    getCar(id): 
        car = getCarFromCache(id)
        if car == null {
            // 从DB获取数据及对应版本号
            car, dbVersion = getCarAndVersionFromDB(id)
            if car != null {
                updateCacheWithCarAndVersion(id, car, dbVersion)
            } else {
                // 缓存null值并设置短过期时间,避免DB穿透
                updateCacheWithNull(id, 60)
                return null
            }
        } else {
            // 校验缓存版本是否与DB一致
            currentDbVersion = getCarVersionFromDB(id)
            if car.version < currentDbVersion || currentDbVersion == -1 { // -1标记已删除
                deleteCarFromCache(id)
                return getCar(id) // 重新查询
            }
        }
        return car
    
  • deleteCar流程:删除DB数据时,将该ID的版本号标记为-1(或一个极大值),再删除缓存。后续GET请求校验版本时会识别数据已失效,不会写入脏缓存。

这种方式无需锁,仅通过版本号实现一致性,性能影响极小。

2. 延迟双删

调整deleteCar的执行逻辑,在首次删除缓存后,异步延迟一段时间再执行二次删除:

deleteCar(id):
    deleteCarFromDB(id)
    deleteCarFromCache(id)
    // 异步线程延迟1-5秒(根据业务DB查询耗时调整)
    asyncDelay(() => { deleteCarFromCache(id) })

原理:即便GET请求在首次缓存删除后写入旧数据,延迟删除会再次清理脏数据,后续请求会重新查询DB(此时已无数据)。
优点是实现简单,无锁开销;缺点是存在短暂的脏数据窗口,需合理设置延迟时间。

3. 细粒度互斥锁(单ID锁)

不用全局锁,仅针对单个Car ID加锁:

  • getCar流程优化:
    getCar(id): 
        car = getCarFromCache(id)
        if car == null {
            lock(id) // 仅对当前ID加锁
            try {
                // 二次检查缓存,避免其他线程已加载数据
                car = getCarFromCache(id)
                if car == null {
                    car = getCarFromDB(id)
                    if car != null { // DB存在才更新缓存
                        updateCacheWithCar(id, car)
                    }
                }
            } finally {
                unlock(id)
            }
        }
        return car
    
  • deleteCar流程同样对该ID加锁,确保DB删除与缓存删除的原子性。
    这种方式仅同一ID的请求会互斥,不同ID请求互不影响,性能远优于全局锁。

4. 主动更新缓存(替代懒加载)

若业务删除操作频繁,可放弃懒加载,改为主动更新策略:

  • 新增/更新Car时,先写DB再同步更新缓存;
  • 删除Car时,先删DB再删缓存;
  • GET请求直接读缓存,缓存不存在则返回数据不存在。
    这种方式彻底避免懒加载的并发问题,但要求所有写操作都能正确触发缓存更新,适合写操作可控的场景。

缓存失效核心实践要点

  • 缓存策略选型:Cache-Aside(懒加载)适合读多写少场景;Write-Through(写DB同时写缓存)适合一致性要求高的场景;Write-Behind(先写缓存异步写DB)适合写频繁场景;
  • 一致性优先级:强一致场景优先选版本号或细粒度锁;允许短暂不一致场景可选延迟双删;
  • 避免缓存穿透:已删除的ID可缓存null值并设置短过期时间,防止大量请求直接打向DB;
  • 热点数据防护:热点ID可设置永不过期(结合主动更新)或加互斥锁,避免缓存失效后引发DB雪崩。

内容的提问来源于stack exchange,提问作者JackG

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 01:12:09