前言:
在实际开发中,MySQL 与 Redis 的双写一致性问题 是一个非常常见又复杂的技术难点。本文将就其产生的原因、触发场景、解决方案、代码示例 进行完整的讲解,帮助大家更好地在实际的项目开发过程中采用合适的方式保证两者间的双写一致。
🔍 一、什么是双写一致性问题?
当我们对数据进行修改时,既要更新数据库(MySQL),又要更新缓存(Redis),这就形成了“双写”。在高并发的情况下,如果更新顺序不合理或出现失败重试,就可能导致 Redis 和 MySQL 的数据不一致。
⚠️ 二、常见不一致的场景
序号 | 场景 | 描述 |
---|---|---|
1 | 先更新数据库,后更新缓存 | 如果更新数据库成功,但更新缓存失败,就造成不一致。 |
2 | 先更新缓存,后更新数据库 | 如果更新缓存成功后数据库失败,缓存就是错误的。 |
3 | 并发更新,读写穿插 | 线程A 删除缓存 -> 线程B 查询缓存无数据 -> 查询数据库 -> 重建缓存(旧数据) -> 线程A 更新数据库 |
4 | 缓存延迟双删未命中 | 删除缓存后,读请求立刻进入,读取了未更新的数据库数据并写入了旧缓存。 |
5 | 异步更新失败 | 使用消息队列等异步方式更新 Redis,如果失败或消费延迟,也会造成不一致。 |
✅ 三、解决方案汇总
方案 | 原理 | 保证机制 | 缺点 | |
---|---|---|---|---|
1 | 延迟双删策略 | 删除缓存 -> 更新数据库 -> 延迟再删缓存 | 有短时间窗口不一致 | |
2 | 消息队列异步更新缓存 | 写库成功后发送消息通知更新缓存 | 最终一致,强依赖 MQ | 增加复杂度 |
3 | 分布式锁更新 | 用分布式锁串行化更新 | 强一致 | 性能受限 |
4 | Binlog + Canal 异步同步缓存 | 监听 binlog 日志推送到 Redis 更新 | 最终一致 | 实时性差 |
5 | 读写全部走缓存,写入数据库异步持久化 | 类似纯缓存架构(写合并) | 不适合强一致业务 | 适合弱一致业务 |
📌 四、推荐方案:延迟双删策略(适用于大多数情况)
步骤流程:
1、删除缓存
2、更新数据库
3、睡眠短时间(如 500ms)
4、再次删除缓存
✅ 为什么有效?
如果在第1次删除后有并发请求重建了旧缓存,第4步能再次删掉这个“脏缓存”。
🧑💻 五、Go 实现示例(延迟双删)
func UpdateUserName(userId int64, newName string) error {
cacheKey := fmt.Sprintf("user:%d", userId)
// Step 1: 删除缓存
_ = redisClient.Del(context.Background(), cacheKey)
// Step 2: 更新数据库
if err := db.Model(&User{}).Where("id = ?", userId).Update("name", newName).Error; err != nil {
return err
}
// Step 3: 延迟后再次删除缓存(避免并发脏数据)
go func() {
time.Sleep(500 * time.Millisecond)
_ = redisClient.Del(context.Background(), cacheKey)
}()
return nil
}
🔍 注意:
睡眠时间 500ms~1s 根据业务调整。
异常重试可以使用 go-redis 的重试机制或加兜底策略。
📌 六、更高级的方案:Binlog + Canal 增量同步缓存
适用于要求 最终一致 且不易修改业务逻辑的大型系统。
原理:
1、MySQL 写入数据。
2、Canal 监听 Binlog 变化。
3、触发缓存更新逻辑(如重建 Redis 对应 key)。
架构图:
App写库
↓
MySQL ——> Binlog ——> Canal ——> Kafka ——> Cache更新消费器
↓
Redis
优势:
对业务零侵入。
最终一致,适合数据查询为主的业务。
🔐 七、使用分布式锁强一致(场景有限)
通过 Redis 的 SETNX 加锁,串行执行更新缓存 + 更新数据库。
lockKey := fmt.Sprintf("lock:user:%d", userId)
ok, _ := redisClient.SetNX(ctx, lockKey, 1, 5*time.Second).Result()
if ok {
defer redisClient.Del(ctx, lockKey)
// 更新DB & 缓存
} else {
// 忽略或重试
}
缺点:
性能损耗高,存在死锁风险。
一般用于重要但低并发场景。
🧠 八、总结对比
方案 | 一致性 | 实时性 | 复杂度 | 推荐业务场景 |
---|---|---|---|---|
延迟双删 | ✅较好 | ✅好 | ⭐低 | 大多数系统推荐 |
分布式锁 | ✅强 | ❌差 | ⭐⭐中 | 强一致性需求 |
Canal同步 | ✅最终 | ❌差 | ⭐⭐⭐高 | 大型系统 |
MQ异步更新 | ✅最终 | ✅中 | ⭐⭐⭐高 | 高并发写入系统 |