mysql并发环境中缓存失效怎么办_mysql一致性方案

来源:这里教程网 时间:2026-02-28 20:41:39 作者:

缓存失效时数据不一致的典型表现

在 MySQL + Redis 架构中,并发写入常导致「缓存未更新但数据库已变」,比如用户 A 更新订单状态为

paid
,写库成功,删缓存也成功;但用户 B 几乎同时读缓存,拿到旧的
unpaid
状态——这不是缓存没删,而是删完之后、新值还没写入缓存前的窗口期被读到了脏数据。

先删缓存再更新数据库?不行,有竞态

看似合理的顺序,在并发下反而更危险:两个请求都走「删缓存 → 更新 DB」,第二个请求的 DB 写入完成后,缓存仍是空的,后续读请求会回源查到第二个请求写入的新值,然后写入缓存——这本身没问题;但若此时第一个请求的缓存重建(比如它带了延迟双删逻辑)把旧值又刷进去了,就彻底错乱。

DEL cache:order:123
(请求 A)
UPDATE orders SET status='paid' WHERE id=123
(请求 A)
DEL cache:order:123
(请求 B)
UPDATE orders SET status='shipped' WHERE id=123
(请求 B)
SET cache:order:123 {status:'paid'}
(请求 A 的延迟重建,覆盖了正确的 shipped)

推荐方案:更新数据库后删除缓存 + 设置过期时间兜底

核心是放弃「强一致」幻想,接受短暂不一致,用「最终一致 + 降低风险」组合拳。关键点不在删缓存时机多精巧,而在如何让错误窗口更小、更可测。

所有写操作统一走「先更新 MySQL,再
DEL
缓存」,不搞延迟双删
缓存必须设
EXPIRE
,比如
SET cache:order:123 {...} EX 300
(5 分钟),避免永久脏数据
读请求遇到缓存 miss,查库后写入缓存时,加一层「版本号 or 时间戳校验」:只当 DB 中的
updated_at
比缓存里记录的更新,才允许写入(需业务表有该字段)
对一致性要求极高的场景(如支付单状态),读请求直接查库,绕过缓存——用开关控制,不是所有读都缓存
UPDATE orders 
SET status = 'paid', updated_at = NOW() 
WHERE id = 123 AND version = 5;

配合应用层检查返回影响行数是否为 1,失败则重试或告警。

监听 binlog 做缓存更新比应用层更可靠

应用代码里删缓存容易漏(比如新增一个 DAO 方法但忘了配缓存清理),而 MySQL 的 binlog 是唯一真实写入源。用

canal
debezium
订阅变更,收到
UPDATE orders
事件后触发
DEL cache:order:${id}
,能消除应用层逻辑分散带来的不一致风险。

binlog 方案不依赖业务代码,改 SQL 就生效 注意事务边界:一个事务含多条语句时,binlog 解析要按事务粒度投递,避免中间状态被消费 消费端需幂等:同一条 binlog 可能重复投递,
DEL
本身是幂等的,但如果是
SET
缓存就要加判断

真正难的不是选哪种方案,而是意识到「缓存永远比数据库慢半拍」,然后在业务可接受范围内,把这半拍控制在毫秒级、可监控、可降级。

相关推荐