0%

MySQL查询缓存

MySQL 查询缓存:机制、优缺点与最佳实践

MySQL 的查询缓存(Query Cache)是一项旨在通过缓存 SELECT 语句的结果集来提升查询性能的机制。然而,它的适用场景有限,且在 MySQL 8.0 中已被移除。以下详细解析其工作原理、失效机制及使用建议。

查询缓存的工作原理

查询缓存的核心逻辑是缓存 SELECT 语句的结果集,当相同查询再次执行时直接返回缓存结果,跳过 SQL 解析、执行计划生成和实际执行步骤。

MySQL查询过程

  1. 缓存触发流程
    • 执行 SELECT 语句时,MySQL 首先对 SQL 进行大小写敏感的哈希计算,生成缓存键。
    • 检查缓存中是否存在该键对应的结果集:
      • 若命中(缓存有效),则验证用户权限后直接返回结果。
      • 若未命中,则执行完整查询流程(解析、优化、执行),并将结果存入缓存。
  2. 缓存存储形式
    • 以键值对形式存储,键为 SQL 语句的哈希值,值为查询结果集。
    • 缓存空间由 query_cache_size 控制,默认分配一块连续内存,按固定大小的块(query_cache_min_res_unit)分配给结果集。

查询缓存的失效机制

查询缓存的最大局限在于极易失效,任何与缓存相关的表发生变更时,关联的所有缓存都会被清空。具体触发条件包括:

  1. 数据变更:对表执行 INSERT/UPDATE/DELETE/TRUNCATE 等操作。
  2. 结构变更:执行 ALTER TABLE/DROP TABLE 等修改表结构的操作。
  3. 缓存空间不足:当缓存满时,会根据 LRU(最近最少使用)策略淘汰旧缓存(Qcache_lowmem_prunes 计数增加)。

注意:即使表数据未实际变更(如 UPDATE t SET col=col),也会触发缓存失效,这是导致缓存命中率低的常见原因。

查询缓存的核心配置参数

参数 作用 推荐值
query_cache_type 控制缓存开关: - OFF:禁用缓存 - ON:启用缓存(默认) - DEMAND:仅缓存带 SQL_CACHE 关键字的查询 读写频繁场景设为 OFFDEMAND
query_cache_size 缓存总内存大小(必须为 1024 的倍数) 建议 0(禁用)或根据实际需求设置(如 64M-256M)
query_cache_limit 单条查询结果的最大缓存大小(超过则不缓存) 避免大结果集占用过多缓存(如 1M)
query_cache_min_res_unit 缓存块的最小分配单位 默认 4KB,大结果集场景可增大(减少碎片)
query_cache_wlock_invalidate MyISAM 表写锁时是否允许读缓存(FALSE 允许,TRUE 等待) 保持默认 FALSE(减少读阻塞)

查询缓存的状态监控

通过 SHOW STATUS LIKE 'Qcache%' 查看缓存使用情况,关键指标解析:

状态变量 含义 分析建议
Qcache_hits 缓存命中次数 数值越高,缓存效果越好
Qcache_inserts 缓存写入次数 Qcache_hits / Qcache_inserts 比值低(如 < 0.5),缓存性价比低
Qcache_lowmem_prunes 因内存不足淘汰的缓存数 频繁增加说明 query_cache_size 过小或碎片过多
Qcache_free_blocks 空闲缓存块数 数值过大(如 > Qcache_total_blocks / 10)说明碎片严重,需执行 FLUSH QUERY CACHE 整理
Qcache_not_cached 未被缓存的查询数 过大可能因 SQL 包含变量、函数等不支持缓存的内容

查询缓存的适用场景与局限性

适用场景:

  • 读多写少的静态数据(如配置表、字典表),且查询模式固定(SQL 完全一致)。
  • 频繁重复执行的简单查询(如 SELECT count(*) FROM t),结果集小且稳定。

局限性:

  1. 缓存命中率低:写操作频繁的表会导致缓存频繁失效,甚至缓存维护开销超过收益。
  2. 不支持动态查询:包含用户变量(@var)、函数(NOW())、存储过程等的查询无法缓存。
  3. 内存碎片问题:频繁的缓存写入和淘汰会产生碎片,降低内存利用率。
  4. MySQL 8.0 已移除:官方认为其维护成本高,且在高并发场景下性能不佳,建议通过应用层缓存(如 Redis)替代。

最佳实践

  1. 禁用查询缓存

    • 对于读写频繁的业务(如电商、社交),直接设置 query_cache_type = OFFquery_cache_size = 0,避免缓存维护开销。
  2. 按需启用缓存

    • 若需保留缓存,将query_cache_type设为DEMAND,仅对特定查询手动开启缓存:

      1
      2
      3
      4
      5
      -- 强制缓存该查询
      SELECT SQL_CACHE id, name FROM static_table WHERE type = 1;

      -- 强制不缓存该查询
      SELECT SQL_NO_CACHE * FROM frequently_updated_table;
  3. 替代方案

    • 用应用层缓存(Redis、Memcached)缓存查询结果,灵活性更高且不受表变更影响。
    • 优化索引和查询语句,减少重复查询的执行时间

欢迎关注我的其它发布渠道