mysql高并发查询慢需用redis缓存分流,核心是精准控制缓存写入、删除时机及键设计,避免脏数据与雪崩;键须含版本和查询条件,删缓存要异步补偿,高频低更新小数据才适合缓存。

MySQL高并发查询慢,光加索引不够用
索引只能解决“找得快”,但扛不住大量重复读请求打到数据库。尤其像商品详情、用户资料这类热点数据,每秒几百次 SELECT 直接穿透到 MySQL,连接池打满、CPU飙升、慢查询日志爆仓——这不是 SQL 写得差,是读流量没分流。
Redis 不是用来“替代 MySQL”的,而是做一层带失效语义的缓存代理。关键在「什么时候写缓存」「什么时候删缓存」「怎么避免脏数据」。
- 别在业务代码里手动
SET+GET:容易漏删、错删,缓存和 DB 状态很快不一致 - 读场景优先查
GET,命中直接返回;未命中再查 MySQL,然后SET回写(注意设过期时间,别用EXPIRE单独调用) - 写操作必须走「先更新 DB,再删缓存」(不是更新缓存),否则并发写时缓存会覆盖新值
-
DEL比SET更安全:删掉比设错值好,下次读自然重建
Redis 缓存键设计要带业务上下文
缓存键不能只拼 ID,比如 "user:123" 看似简单,但一旦字段变更(如加了脱敏逻辑)、分表后 ID 语义变化,或需要按条件组合查询(如 "user:123:profile_v2"),旧键就成脏数据黑洞。
键名必须包含版本号或结构标识,且和 SQL 查询条件严格对齐。否则一个 SELECT * FROM user WHERE id = ? AND status = 'active' 缓存在 "user:123",但实际查的是带状态过滤的结果,缓存就失效了。
- 推荐格式:
"user:detail:v2:id:123"或"query:user_active_by_id:123" - 避免用 JSON 字符串做 key(如
"user:{\"id\":123}"),序列化差异、空格、顺序都会导致 key 不一致 - 不要把整个大对象塞进一个 key,拆成
"user:base:123"、"user:stats:123",按需加载,降低单次缓存失效影响面
删缓存失败会导致雪崩式穿透
常见错误是写完 MySQL 就以为完事了,结果 Redis 连接超时、集群节点失联,DEL 命令根本没发出去。这时缓存一直挂着旧值,或者压根没删干净,后续所有读都命中脏数据,直到过期——而过期时间如果设了几小时,问题就藏得更深。
真正的容错不是靠重试,是靠「异步补偿 + 本地标记」:
- 写 DB 成功后,往本地消息队列(如 Kafka)或延迟任务表写一条
cache_invalidate记录,由独立消费者保证最终删除 - 同时在应用内存中标记该 key “待删除”(比如用
ConcurrentHashMap存个"user:123"),下次读到这个 key 时主动触发重建,不走缓存 - 别依赖 Redis 的
PUB/SUB做缓存失效:网络分区时收不到消息,且无法保证消费顺序
读写分离不是让 Redis 承担全部读压力
很多人一上 Redis 就把所有 SELECT 都切过去,结果 Redis 内存暴涨、RDB 备份卡顿、主从同步延迟升高,反而拖垮整体稳定性。Redis 是缓存,不是只读副本。
真正该放进去的,是满足「高频、低更新、小体积、强一致性容忍」的数据。比如用户头像 URL、配置开关、地区字典。订单列表、实时库存、资金流水这些,要么不做缓存,要么用更细粒度(如缓存单条订单状态,而不是整个订单页)。
- 用
redis-cli --bigkeys定期扫描,删掉 >100KB 的 value,它们通常来自误缓存的 list 或 HTML 片段 - 监控
evicted_keys指标:持续增长说明内存不够或 key 设计不合理,不是简单加机器能解决的 - MySQL 主库写压力大时,别指望 Redis 分担写——它不处理事务、不支持 join,强行写入只会让一致性更难维护
缓存穿透、击穿、雪崩这些词背后,其实都是同一个问题:缓存生命周期和业务数据生命周期没对齐。对齐的关键不在工具,而在每次写操作发生时,你是否清楚知道哪些 key 必须失效、哪些可以保留、哪些压根不该进缓存。











