索引没建对,缓存再快也白搭;必须先确保查询由覆盖索引支撑(where/order by/select字段全包含),再考虑缓存,否则缓存无法解决io与cpu瓶颈。

索引没建对,缓存再快也白搭
缓存(比如 Redis)只能加速「已经能快速查出来的数据」,如果 SQL 本身走全表扫描、EXPLAIN 显示 type=ALL、rows 动辄几十万,那哪怕把结果缓存 1 小时,下一次未命中缓存的请求照样拖垮数据库。真实线上案例里,70% 的“缓存无效”问题,根子都在索引没覆盖查询条件或顺序错——比如 WHERE status = ? AND created_at > ? ORDER BY id,却只给 status 单独建了索引,created_at 和 id 完全没进索引,MySQL 只能先扫出所有 status 匹配的行,再内存排序,缓存根本救不了这个 IO+CPU 双爆的场景。
哪些查询值得加缓存?先看索引是否“覆盖”
真正适合缓存的查询,往往满足两个硬条件:高频、稳定、且已由**覆盖索引**支撑。所谓覆盖索引,就是 SELECT 的所有字段 + WHERE/ORDER BY 用到的字段,全部包含在同一个索引里,让 MySQL 连主表数据页都不用读。例如:
SELECT user_id, order_no, amount FROM orders WHERE shop_id = ? AND status = ? ORDER BY created_at DESC;
对应建复合索引:ALTER TABLE orders ADD INDEX idx_shop_status_time (shop_id, status, created_at DESC, user_id, order_no, amount);
- 这样查询走索引就能拿到全部结果,
Extra字段显示Using index,IO 极低,才值得扔进 Redis 缓存 - 如果索引里漏了
amount,MySQL 就得回表查聚簇索引,延迟波动大,缓存命中的收益被抵消 - 缓存 key 设计也要对齐索引字段,比如
orders:shop_123:status_1,避免缓存粒度太粗(全量缓存)或太碎(单行缓存)
缓存失效和索引更新不是一回事
很多人误以为“我改了数据,缓存自动失效”,其实完全无关——InnoDB 更新数据时会维护 B+ 树索引,但不会通知 Redis。必须由应用层主动清理或设 TTL。更危险的是:如果索引设计导致查询结果逻辑变更(比如新增一个 WHERE is_deleted = 0 条件但没加到索引里),旧缓存可能长期返回错误数据,而 DB 层毫无感知。
- 写操作后,优先清缓存,而不是等 TTL;尤其涉及金额、状态类字段
- 避免用「更新即删缓存」的简单策略——如果并发写同一记录,可能因时序问题导致缓存击穿,建议用延迟双删或订阅 binlog
- 索引重建(
OPTIMIZE TABLE或ALTER TABLE ... ENGINE=InnoDB)不触发缓存失效,但可能改变查询执行计划,需同步验证缓存 key 是否仍有效
别拿缓存当索引缺陷的遮羞布
见过太多团队在慢查询报警后第一反应是“加个 Redis”,结果缓存掩盖了真实瓶颈:比如一个分页查询 OFFSET 10000 LIMIT 20,索引虽存在,但深度遍历导致延迟飙升,缓存只能缓解第一页,后面翻页照样超时。这种场景该做的是改用游标分页(WHERE id > ? ORDER BY id LIMIT 20)+ 覆盖索引,而不是堆缓存。
- 缓存适合解决「读多写少 + 查询模式固定」的问题;索引优化解决「任意条件组合下都能快速定位」的问题
- 监控时要分开看:Redis 的
get_hits / get_misses比率高 ≠ 数据库健康;MySQL 的Handler_read_next持续飙升,说明索引没发挥应有作用 - 最易被忽略的一点:
JOIN多表时,每张表的驱动顺序和关联字段索引质量,直接影响最终是否能走覆盖索引——这一步没调好,缓存连入口都摸不到











