索引未生效时缓存会放大性能问题,因缓存固化了全表扫描结果;需通过EXPLAIN验证索引使用,合理配置innodb_buffer_pool_size,优先用覆盖索引减少磁盘访问,禁用已废弃的Query Cache。

索引没生效时,缓存反而放大性能问题
MySQL 查询走不上索引,SELECT 却被应用层或代理(如 Redis、ProxySQL)缓存了结果,会导致「缓存永远返回旧的慢查询结果」。典型表现是:数据明明已更新,但接口响应时间依然高,且 EXPLAIN 显示 type=ALL 或 key=NULL。
实操建议:
- 上线前必查
EXPLAIN,尤其关注key、rows、Extra字段;rows接近表总行数基本等于没走索引 - 缓存 key 设计要包含能触发索引的条件字段,例如缓存
user:profile:{user_id}比user:all_profiles更安全 - 对未加索引的
WHERE字段做缓存,等同于把全表扫描结果固化——别这么做
innodb_buffer_pool_size 设置不当,索引和缓存都在抢内存
InnoDB 缓冲池(innodb_buffer_pool_size)本质就是 MySQL 的「主内存缓存」,它负责缓存数据页和索引页。如果设得太小,索引节点频繁换入换出;设得太大,又会挤压 OS 文件缓存和应用层缓存(如 PHP-FPM 内存、Redis 内存),反而引发 swap 或 OOM。
实操建议:
- 生产环境建议设为物理内存的 50%–75%,但上限不超过 80%;可通过
SHOW ENGINE INNODB STATUS查看Buffer pool hit rate,持续低于 99% 就该调大 - 避免与 Redis 共用同一台机器且都吃满内存;若必须共存,给 Redis 配
maxmemory+maxmemory_policy=volatile-lru,防止其无节制膨胀 -
innodb_buffer_pool_instances建议按 CPU 核数设置(如 8 核设为 8),减少内部锁争用
覆盖索引 + SELECT 字段精简,让查询不碰磁盘也不进应用层缓存
当索引包含查询所需全部字段(即覆盖索引),MySQL 可直接从 B+ 树叶子节点返回结果,连主键回表都省了。此时若再在应用层加一层缓存,属于冗余存储+额外序列化开销。
实操建议:
- 用
EXPLAIN确认Extra字段含Using index,而非Using where; Using index(后者仍需回表) - 写 SQL 时显式列出字段,别用
SELECT *;否则即使有索引,也可能因新增列导致覆盖失效 - 对高频、低更新的维度表(如
region、status_code),可建联合覆盖索引:CREATE INDEX idx_status_name ON status (code, name);
Query Cache 已被弃用,别再依赖它协调索引与缓存
MySQL 5.7 默认关闭、8.0 直接移除了 query_cache_type 和相关参数。它的机制是「SQL 文本完全匹配 + 表无变更才复用」,在高并发更新场景下失效频繁,且锁粒度粗(全局锁),反而成瓶颈。
实操建议:
- 确认
SELECT @@have_query_cache;返回NO,或SHOW VARIABLES LIKE 'query_cache%';全为空值——别白费力气调参 - 替代方案:用客户端缓存(HTTP Cache-Control)、代理层缓存(Varnish)、或业务层缓存(Redis + 主键/条件组合 key)
- 注意:这些外部缓存无法感知 MySQL 索引变化,所以更新操作后必须主动
DEL或SET对应 key,不能只依赖过期时间











