MySQL 缓存深入剖析

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/valada/article/details/100588219

最近在公司组织了一次 SQL 优化大赛,出现了一个问题引起了一位同事的不爽,为什么我优化出来 SQL 跟他的一样,为什么时间会相差那么大?并且都是加了 SQL_NO_CACHE 的情况下。

最近在公司组织了一次 SQL 优化大赛,出现了一个问题引起了一位同事的不爽,为什么我优化出来 SQL 跟他的一样,为什么时间会相差那么大?并且都是加了 SQLNOCACHE 的情况下。

其实在校验结果的时候,那位同事的 SQL 是第一个跑的,导致后面相同或者相似的 SQL 会比他的快很多。What?明明已经禁用了 querycache。也许是大家极少进行 SQL 优化,对 Innodb 的原理不太清楚,包括网上很多博客,其实都忽略了一点,就是在 Innodb 引擎中,不只是 querycache,还有一个 buffer pool,翻译成中文就是一个缓存,一个缓冲区。

query_cache 到底是个啥?能干嘛?

query_cache 存储 SELECT 语句的文本以及发送到客户端的相应结果。如果稍后收到相同的语句,则服务器将从查询缓存中检索结果,而不是再次解析和执行语句。查询缓存在会话之间共享,因此可以发送由一个客户端生成的结果集以响应由另一个客户端发出的相同查询。查询缓存在您拥有不经常更改且服务器接收许多相同查询的表的环境中非常有用。这是许多基于数据库内容生成许多动态页面的 Web 服务器的典型情况。

命中 cache 的具体要求如下:

  1. 查询必须完全相同(字节为字节)才能看作相同。另外,由于其他原因,可以将相同的查询字符串视为不同。使用不同数据库,不同协议版本或不同默认字符集的查询被视为不同的查询,并单独缓存;
  2. 查询是外部查询的子查询不会缓存;
  3. 在存储的函数,触发器或事件的主体内执行的查询不会使用缓存;
  4. querycache 不返回过时数据,如果表更改(包括表结构和数据的更改),则使用该表的所有 querycache 都将变为无效并从 query_cache 中删除。

说了这么多,并不是我在引导你不去使用 querycache,其实 MySQL 在从 MySQL 5.7.20 开始,已经开始废弃 querycache,并在 MySQL 8.0 中删除,主要原因是不能与多核计算机上的高吞吐量工作负载进行扩展,解释如下:

Assuming that scalability could be improved, the limiting factor of the query cache is that since only queries that hit the cache will see improvement; it is unlikely to improve predictability of performance. For user facing systems, reducing the variability of performance is often more important than improving peak throughput。

假设可以提高可伸缩性,query cache 的限制因素是在于只有访问缓存的查询才会看到明显的提升; 它跟提高性能的可预测性并不一样。 对于面向用户的系统,降低性能的可变性通常比提高峰值吞吐量更重要。

也就是说 query_cache 对于性能的提升可变性太高,比起能够提升峰值的吞吐量,似乎有些得不偿失。

介绍了这么多 query_cache,也是要退出历史舞台的东西了,要介绍真正的主角应该是 buffer pool。

buffer pool 又是个嘛呢?

缓冲池是主存储器中的一个区域,用于在 InnoDB 访问时缓存表和索引数据。缓冲池允许直接从内存处理常用数据,从而加快处理速度。在专用服务器上,通常会将最多 80%的物理内存分配给缓冲池;

为了提高大容量读取操作的效率,缓冲池被分成可以容纳多行的页面。为了提高缓存管理的效率,缓冲池被实现为链接的页面列表; 使用 LRU 算法的变体,很少使用的数据在缓存中老化 ;

了解如何利用缓冲池将频繁访问的数据保存在内存中是 MySQL 调优的一个重要方面。

缓冲池 LRU 算法

当需要空间将新页面添加到缓冲池时,最近最少使用的页面被逐出,并且新页面被添加到列表的中间。此中点插入策略将列表视为两个子列表:

  • 在头部是最近访问过的新(“ 年轻 ”)页面的子列表
  • 在尾部是最近访问的旧页面的子列表在这里插入图片描述该算法在新子列表中保留了大量页面。旧子列表包含较少使用的页面; 这些页面是驱逐的候选人 。

默认情况下,算法操作如下:

  • 3/8 的缓冲池专用于旧子列表。
  • 列表的中点是新子列表的尾部与旧子列表的头部相交的边界。
  • 当 InnoDB 将页面读入缓冲池时,它最初将其插入中点(旧子列表的头部)。可以读取页面,因为它是用户启动的操作(如 SQL 查询)所必需的,或者是由自动执行的预读操作的一部分 InnoDB。
  • 访问旧子列表中的页面使其 “ 年轻 ”,将其移动到新子列表的头部。如果由于用户启动的操作需要读取页面,则第一次访问立即发生,页面变为年轻。如果由于预读操作而读取了页面,则第一次访问不会立即发生,并且可能在页面被驱逐之前根本不会发生。
  • 随着数据库的运行,在缓冲池的页面没有被访问的“ 年龄 ”通过向列表的尾部移动。新旧子列表中的页面随着其他页面的变化而变旧。旧子列表中的页面也会随着页面插入中点而老化。最终,仍然未使用的页面到达旧子列表的尾部并被逐出。

默认情况下,查询读取的页面会立即移动到新的子列表中,这意味着它们会更长时间地保留在缓冲池中。例如,为 mysqldump 操作或 SELECT 没有 WHERE 子句的 语句 执行的表扫描可以将大量数据带入缓冲池并逐出相同数量的旧数据,即使新数据从未再次使用过。类似地,由预读后台线程加载并仅访问一次的页面将移动到新列表的头部。这些情况可以将经常使用的页面推送到旧的子列表中,在那里它们会被驱逐。

但是这里需要清楚我怎么知道我的查询到底是不是在 buffer 里面查出来的,也给上面那位同事解释清楚两个 SQL 跑出较大时间差距的原因。

可以通过SHOW ENGINE INNODB STATUS查询有关缓冲池操作的指标。缓冲池指标位于标准监视器输出 BUFFER POOL AND MEMORY 部分, InnoDB 显示类似于以下内容:

----------------------BUFFER POOL AND MEMORY----------------------Total large memory allocated 2198863872Dictionary memory allocated 776332Buffer pool size   131072Free buffers       124908Database pages     5720Old database pages 2071Modified db pages  910Pending reads 0Pending writes: LRU 0, flush list 0, single page 0Pages made young 4, not young 00.10 youngs/s, 0.00 non-youngs/sPages read 197, created 5523, written 50600.00 reads/s, 190.89 creates/s, 244.94 writes/sBuffer pool hit rate 1000 / 1000, young-making rate 0 / 1000 not0 / 1000Pages read ahead 0.00/s, evicted without access 0.00/s, Random readahead 0.00/sLRU len: 5720, unzip_LRU len: 0I/O sum[0]:cur[0], unzip sum[0]:cur[0]

解释如下:在这里插入图片描述

在理想情况下(保证只有你在操作数据库并且扫描的行数可观),可以在执行查询前后执行 SHOW ENGINE INNODB STATUS,关注 Buffer pool hit rate 和 I/O sum 的变化(需要注意的是 I/O sum 保存的是最近 50 秒的数据)。

  • 如果 Buffer pool hit rate 执行前后保持一致,并且 I/O sum 执行后增大了,基本上就是获取了 buffer pool 中的数据了;
  • 如果 Buffer pool hit rate 执行后明显降低了,说明有磁盘读取的数据进来了,这种情况基本上是第一次执行该 SQL,或者很久没有执行相关 SQL,buffer pool 中已经淘汰了这部分数据。

但是并不绝对,仅供参考,毕竟这个数据是对整个 buffer pool 进行分析的,不针对单一 SQL。

总结:上面这位同事碰到的这个问题,除非是 DBA 或者是数据库方面的专家,大多数后台开发基本上都会忽略这一点(在和一些工作了几年的 Java 开发交流过程中,发现他们在对数据库查询进行优化的时候,基本上都不知道 buffer pool 的存在,这个其实对优化会有很大的影响),大家在碰到这种问题的时候,可以直接英文 Google,如果没有梯子,直接到 stackoverflow 或者 stackexchange 基本上都能找到你的疑难绝症,当然,官方文档也是必不可少的,以上这篇文章主要是参考官方文档进行撰写,并且做了一些翻译。

参考:https://dev.mysql.com/doc/refman/5.7/en/innodb-buffer-pool.htmlhttps://dba.stackexchange.com/questions/199397/why-query-with-sql-no-cache-runs-slower-on-the-first-runhttps://mysqlserverteam.com/mysql-8-0-retiring-support-for-the-query-cache/

阅读全文: http://gitbook.cn/gitchat/activity/5d71b78b6b6e7537032ab40c

您还可以下载 CSDN 旗下精品原创内容社区 GitChat App ,阅读更多 GitChat 专享技术内容哦。

FtooAtPSkEJwnW-9xkCLqSTRpBKX

猜你喜欢

转载自blog.csdn.net/valada/article/details/100588219