MySQL查询缓存

什么是MySQL查询缓存

MySQL 缓存机制就是缓存 sql 文本及缓存结果,用 KV 形式保存再服务器内存中,如果运行相同的 sql,服务器直接从缓存中去获取结果,不需要在再去解析、优化、执行 sql。

如果这个表修改了,那么使用这个表中的所有缓存将不再有效,查询缓存值得相关条目将被清空。表中得任何改变是值表中任何数据或者是结构的改变,包括 insert, update, delete, truncate, alter table, drop table 或者是 drop database 包括那些映射到改变了的表的使用 merge 表的查询,显然,对于频繁更新的表,查询缓存不合适,对于一些不变的数据且有大量相同 sql 查询的表,查询缓存会节省很大的性能。

命中条件

缓存存在一个 hash 表中,通过查询 SQL,查询数据库,客户端协议等作为 key,在判断命中前,mysql 不会解析 SQL,而是使用 SQL 去查询缓存,SQL 上的任何字符的不同,如空格,注释,都会导致缓存不命中。如果查询有不确定的数据 like now(),current_date(),那么查询完成后结果者不会被缓存,包含不确定的数的是不会放置到缓存中。

工作流程

  1. 服务器接收 SQL,以 SQL 和一些其他条件为 key 查找缓存表。
  2. 如果找到了缓存,则直接返回缓存。
  3. 如果没有找到缓存,则执行 SQL 查询,包括原来的 SQL 解析,优化等。
  4. 执行完 SQL 查询结果以后,将 SQL 查询结果缓存入缓存表。

MySQL查询缓存配置

下面是关于 Query Cache 相关参数:

  • query_cache_size: 设置 Query Cache 所使用的内存大小,默认值为 0,大小必须是 1024 的整数倍,如果不是整数倍,MySQL 会自动调整降低最小量以达到 1024 的倍数。
  • query_cache_type: 控制 Query Cache 功能的开关,可以设置为 0(OFF),1(ON) 和 2(DEMAND) 三种:0 表示关闭 Query Cache 功能,任何情况下都不会使用 Query Cache;1 表示开启 Query Cache 功能,但是当 SELECT 语句中使用的 SQL_NO_CACHE 提示后,将不使用 Query Cache;2(DEMAND) 表示开启 Query Cache 功能,但是只有当 SELECT 语句中使用了 SQL_CACHE 提示后,才使用 Query Cache。
  • query_cache_limit: 允许 Cache 的单条 Query 结果集的最大容量,默认是 1MB,超过此参数设置的 Query 结果集将不会被 Cache。
  • query_cache_min_res_unit: 设置 Query Cache 中每次分配内存的最小空间大小,也就是每个 Query 的 Cache 最小占用的内存空间大小。
  • query_alloc_block_size: 缓存的块大小,默认为 8192 字节。
  • query_cache_wlock_invalidate: 控制当有写锁加在表上的时候,是否先让该表相关的 Query Cache 失效,1(TRUE),在写锁定的同时将使该表相关的所有 Query Cache 失效。0(FALSE),在锁定时刻仍然允许读取该表相关的 Query Cache。
  • Qcache_lowmem_prunes: 这是一个状态变量(show status),当缓存空间不够需要释放旧的缓存时,该值会自增。
  • Qcache_free_blocks: 目前还处于空闲状态的 Query Cache 中内存 Block 数目。
  • Qcache_free_memory: 目前还处于空闲状态的 Query Cache 内存总量。
  • Qcache_hits: Query Cache 命中次数。
  • Qcache_inserts: 向 Query Cache 中插入新的 Query Cache 的次数,也就是没有命中的次数。
  • Qcache_lowmem_prunes: 当 Query Cache 内存容量不够,需要从中删除老的 Query Cache 以给新的 Cache 对象使用的次数。
  • Qcache_not_cached: 没有被 Cache 的 SQL 数,包括无法被 Cache 的 SQL 以及由于 query_cache_type 设置的不会被 Cache 的 SQL。
  • Qcache_queries_in_cache: 目前在 Query Cache 中的 SQL 数量。
  • Qcache_total_blocks: Query Cache 中总的 Block 数量。

MySQL查询缓存特点

MySQL 的查询缓存有如下特点:

  1. 完全相同的 SQL 才会命中缓存

    在查询缓存中搜索结果前,MySQL 不会对 SQL 进行解析,因此,查询缓存的查找方式是字节匹配。也就是说,如果 SQL 中包含不确定内容、多余的空格都会被认为是不同的 SQL。

    当服务器端接收到查询包后,系统就会检查查询缓存中是否有该查询,因此利用查询缓存可以省去 SQL 解析和处理操作。MySQL 得到结果集后,将结果集以包的形式发送到客户端,在将包发送到客户端之前会将包保存到查询缓存中。是否将结果集插入到查询缓存取决于查询 SQL,能够插入到查询缓存的对象如下第 2 点所示。

  2. MySQL的缓存对象如下:

    1. 只缓存 SELECT 语句。SHOW 命令和存储程序不会被缓存。

    2. 不能缓存预编译语句(prepared statement)和游标。 查询缓存中保存的是查询语句和结果集,而预编译语句中存在替代符和额外的参数,游标从块中读取结果。

    3. 查询语句不能包含动态内容。多次执行某 SQL,必须能够返回相同的结果集,因此查询中不能包含像 UUID(), RAND(), CONNECTION_ID() 这样的函数。

    4. SQL 中包含定义函数和自定义变量不会被缓存。

    5. 对系统表的查询不会被缓存。

    6. 非自动提交(显示使用 BEGIN…END)事务中的 SQL 不会被缓存。

    7. 使用 TEMPORARY 表的 SQL 不会被缓存。

    8. 不使用任何表的 SQL 不会被缓存。

    9. 在下面的 SELECT 操作也不会被缓存:

      SELECT …IN SHARE MODE SELECT …FOR UPDATE SELECT …INTO OUTFILE … SELECT …INTO DUMPFILE … SELECT * FROM …WHERE autoincrement_col IS NULL
  3. 表内容更改时缓存失效

    修改表的所有操作(DELETE/INSERT/UPDATE 等等),都会导致缓存中该表的所有内容(SQL 和结果集)被一次清空。很有可能这些操作并没有改变 SQL 的结果集,但 MySQL 无法验证哪些 SQL 会影响缓存而哪些 SQL 不会影响。也是由于这点,MySQL 的缓存利用率不是很高。对于写操作频繁的应用,查询缓存的命中率会相当的低。如果缓存中存在某表的大量 SQL,多少也会降低该表的更新速度。

  4. 缓存碎片

    随着缓存量的增多,查询缓存会产生碎片,这将降低缓存性能。状态变量 Qcache_free_blocks 描述了缓存中的空闲块,该值越大表示碎片越多。可以用 FLUSH QUERY CACHE 命令来整理碎片,而对于大缓存,该操作会长时间阻塞查询缓存。

查询缓存的优缺点

  1. 不需要对 SQL 语句做任何解析和执行,当然语法解析必须通过先,直接从 query cache 中获取查询结果;
  2. 查询缓存的判断规则不够智能,也提高了查询缓存的使用门槛,降低其效率;
  3. query cache 的启用,会增加检查和清理 query cache 中记录集的开销,而且存在 SQL 语句缓存的表,每一张都只有一个对应的全局锁。

查询缓存不适用场景

  1. 子查询或者外层查询;
  2. 存储过程、存储函数、触发器、event 中调用的 SQL,或者引用到这些结果的;
  3. 包含一些特殊函数时,例如:BENCHMARK()、CURDATE()、CURRENT_TIMESTAMP()、NOW()、RAND()、UUID() 等等;
  4. 读取 mysql、INFORMATION_SCHEMA、performance_schema 库数据的;
  5. 类似 SELECT…LOCK IN SHARE MODE、SELECT…FOR UPDATE、SELECT…INTO OUTFILE/DUMPFILE、SELECT…WHRE…IS NULL 等语句;
  6. SELECT 执行计划用到临时表(TEMPORARY TABLE);
  7. 未引用任何表的查询,例如 SELECT 1+1 这种;
  8. 产生了 warnings 的查询;
  9. SELECT 语句里加了 SQL_NO_CACHE 关键字;

总结

  1. MySQL 的查询缓存利用率很低,原因是每当有修改表内容操作时,缓存中所有与该表相关的内容全部要被清空。
  2. 查询缓存是一次申请 query_cache_size 大小的内存,而不是随查询的插入动态申请,这样提升了系统性能,因为申请、释放内存的操作很慢。
  3. 查询缓存的空闲块是有序的,因为较小的块会被经常使用,同样为了性能考虑。
  4. 为充分利用内存,某缓存块填充数据后,如果还有空闲空间,则将空闲空间回收。
  5. 缓存替换策略是,当缓存没有空闲块时,系统将最老(最近没有被使用)的查询块剔除。