索引没生效时,缓存反而放大性能问题
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,不能只依赖过期时间 索引和缓存不是叠加使用就一定快,关键在数据访问路径是否真正收敛到内存层级。最容易被忽略的是:你以为缓存保护了慢查询,其实只是把问题延迟暴露了。
