当您执行两个相似但不相同的选择请求时,MySQL/InnoDB 缓存如何在内部工作?

Sud*_*tty 5 mysql innodb query-cache

我对 Innodb 缓冲池的工作方式有些困惑。

假设我有一个包含 15 条记录的表,如果我执行:select ... between 1 to 10那么该结果将缓存在缓冲区(?)中。我的问题是:如果我select .... between 1 to 15下次执行,那么它将如何获取记录。它是从缓冲区中选择 10 条记录,从磁盘中选择 5 条记录吗?

jyn*_*nus 6

您的问题“MySQL 选择请求如何在内部工作”是一个非常广泛的主题。但是,我可以或多或少地给出关于缓存的“当您选择记录 1 到 10,然后依次选择 1-15 时会发生什么”的一个不完全准确但简单的概述。

您必须了解的第一件事是,在功能上,MySQL 可以分为2 个独立的层,即“前端”或 SQL 层,以及 ENGINE 层,它们仅通过一个低级(记住我是在简化事物)基于行进行通信API。您可以使用 InnoDB 或其他可插拔引擎来存储行(让我们只关注那个)。缓存可以发生在 SQL 或引擎级别:

SQL层

在 SQL 层有几种缓存机制,让我们关注查询结果缓存,称为Query Cache。它允许在内存中分配一定数量的空间来返回查询结果,而无需进入引擎。它是一种非常高级的缓存方法,必须与实际存储的数据保持一致,而且它的实现非常简单,从而导致了许多实际问题:

  • 它需要一个全局的写锁,这会杀死所有的并行性并在中到高负载下产生高争用,即使是少量的写入
  • 它可以减缓未命中
  • 驱逐也可能是有害的,一旦写入表,所有结果都会失效,即使结果不会受到影响
  • 如果返回相同的结果,但查询略有不同,则视为2个不同的查询,将相同的查询存储两次
  • 不支持分区表,Galera等集群解决方案也不支持
  • 并非所有查询都是可缓存的(例如不确定性查询),并且它们的空间数量是有限的

在您的情况下,执行了 2 个不同的查询,查询缓存将无用,您将有 2 次未命中,然后是 2 次单独的结果插入(假设它们是可缓存的等)

许多人建议禁用查询缓存并将额外的内存用于其他缓冲区。确切的影响将取决于您的特定工作负载(您应该监控命中/未命中/驱逐和查询延迟)。如果你仍然需要查询缓存,你可以选择像 memcache 这样的东西,更接近应用层。甚至 MySQL 也集成了一个插件来运行 memcache 作为 InnoDB 的前端,作为服务器的一部分。

分离缓存,你可以自己控制缓存结果的TTL。还有许多其他机制可以处理这个问题,例如ProxySQL或在应用程序上实现您自己的机制。

数据库

InnoDB 实现了它自己的复杂缓存系统,用于在缓冲池(它的页面缓存)中更快地检索行,这是一个低得多的缓存。通常,InnoDB 尝试避免(绕过)文件系统缓存并实现自己的行缓存系统。默认页面大小为 16KB(尽管可以更改)并且它在磁盘上的结构与在内存中的结构相同。一个页面应该足够大以存储 2 个或更多的数据行(对于可变大小的字段,一行可以大于 8K,但那是另一回事)。当一个页面在磁盘上被访问时,它被复制到缓冲池中。事实上,由于一些优化,在很多情况下,不仅读取单个页面,而是多个连续页面以减少 IO 操作

因此,当您读取空缓冲池中的前 10 行时,可能只有这些页面与您想要的行一起加载到内存中,而且可能还有更多;在许多情况下,完全扩展和下一个。所有这些都需要进行调整,并取决于您拥有的访问模式类型。

当您再次阅读时,您可能会发现内存中已经存在相同的行,因此您减少了磁盘 I/O。如果缺少某些页面,则需要进行一次(或多次)磁盘 IO 操作。当您运行相同的查询两次并获得更快的响应时,通常会看到这种情况。虽然行缓存只是加速的一部分——你还有线程缓存、表缓存、字典缓存、索引缓存......(在 SQL 和引擎级别)。

但是,当您完成第二次查询时,可能会发生行已被逐出 - 逐出遵循相对复杂的算法并且它也是高度可调的,但它正在 - 简化它 - LRU(最近最少使用)实现,这意味着如果另一个最近访问的数据进入内存,它可以驱逐很少使用的数据。

这里有一个关于 InnoDB 缓冲池如何工作的分步总结:

其他引擎可以做不同的事情,例如,MyISAM 和 TokuDB 都有索引缓存,但它们都更多地依赖文件系统缓存来获取数据。