当MySQL查询缓存遇到尴尬:教训与启发

2023年 9月 28日 21.3k 0

1. 引言

MySQL查询缓存在早期被设计出来,用于改善数据库性能,但是查询缓存在动态数据环境下逐渐显露出一些问题,导致其逐渐被废弃和移除。

本文从MySQL查询缓存的引入初衷、工作原理,以及早期的流行和问题入手,探讨一下查询缓存存在的一些核心问题,以及从中能够带给我们的经验和启发

2. 什么是MySQL的查询缓存

2.1 MySQL引入查询缓存的初衷

引入MySQL查询缓存的初衷源于改善数据库性能和降低查询执行成本的迫切需求。在早期MySQL版本中,查询的执行成本相对较高,尤其是在处理复杂查询语句或大量数据的情况下。

原因 说明
查询解析和优化 数据库需要解析和优化查询,选择合适的执行计划,这需要计算资源和时间。
磁盘IO 读取和写入磁盘上的数据通常是相对较慢的操作,尤其是对于大型数据集。
大数据集的处理 查询涉及大量数据时,数据库需要花费更多时间来处理数据,特别是排序和聚合操作。
复杂查询语句 复杂的查询语句,如多表连接和子查询,通常需要更多时间来执行,因为涉及多次数据访问。

这些问题在早期MySQL版本中更为显著,因此,为了改善性能并降低系统负载,引入了查询缓存机制。

查询缓存允许MySQL将查询结果存储在内存中,以便在后续相同查询被执行时,可以直接返回缓存的结果,而不必再次执行查询。这减少了数据库服务器的负载,减轻了对系统资源的需求,并提高了响应时间,特别是对于频繁执行相同查询的应用程序来说,效果明显。

2.2 查询缓存的工作原理

查询缓存的原理是将查询结果存储在内存中,以便在后续相同查询被执行时,可以直接返回缓存的结果,而不必再次执行查询。

查询缓存是一个键值对存储结构,其中查询语句的文本作为键,查询结果作为值。当一个查询被执行时,MySQL首先检查缓存中是否已经存在相同文本的查询,如果存在,MySQL可以直接返回缓存中的结果,而不必重新执行查询。

当任何与查询结果相关的表发生写操作(INSERT、UPDATE、DELETE)时,与这些表相关的查询缓存都会失效。当查询缓存大小达到限制时,MySQL会优先淘汰最久未使用的查询结果,以腾出空间来存储新的查询结果。

image.png

可以看到,MySQL的缓存设计,是一个常见的设计,将请求的结果保存下来,避免下次同样请求过来后重复查询,基于此降低系统的负载,同时提高性能速度。

3. 查询缓存的流行与问题

3.1 查询缓存的流行

查询缓存在早期MySQL版本中比较流行,它被广泛用于尝试提高查询性能。在早期,MySQL的查询缓存功能是默认启用的,并且为开发人员提供了一个简单的性能优化工具。以下是查询缓存在早期MySQL版本中的一些配置:

  • 默认启用:在某些早期的MySQL版本中,查询缓存是默认启用的,这意味着它会自动处理查询结果的缓存,无需额外配置。
  • 简单配置:配置查询缓存通常是相对简单的,只需设置几个参数,如query_cache_sizequery_cache_type,就可以控制查询缓存的大小和启用/禁用状态。
  • 性能提升:对于某些特定的工作负载和应用程序,查询缓存可以显著提高查询性能,因为它允许数据库直接返回缓存的结果,而无需重新执行查询。
  • 广泛使用:由于其简单性和性能优势,查询缓存在早期MySQL版本中被广泛使用,特别是对于需要频繁执行相同查询的应用程序。
  • 3.2 查询缓存存在的问题

    虽然查询缓存在某些情况下可以提供性能改进,但是在高并发和数据变更频繁的情况下,它存在的一些问题,如缓存粒度、内存消耗等,可能会带来性能问题,甚至降低整体系统性能,下面我们来详细描述下。

    3.2.1 缓存粒度问题

    查询缓存以整个查询语句作为缓存的单位,一旦与查询相关的任何表发生写操作(如INSERT、UPDATE、DELETE),与这些表相关的查询缓存将立即失效。这包括了整个查询缓存,而不仅仅是与写操作相关的查询结果。

    在数据频繁变更的场景下,将导致缓存命中率的下降,资源的浪费以及性能的下降,让整个系统还不如启用缓存前的表现。

        缓存命中率下降:由于缓存的粒度太大,即使查询中的一小部分数据变化,整个查询缓存也会失效。这降低了查询缓存的命中率,因为大部分查询可能只需要数据表一部分结果,但仍需要重新执行整个查询。

        资源浪费:频繁的缓存失效和重新生成可能会浪费系统资源,包括CPU和内存。系统需要不断地维护缓存,即使只有少量数据发生变化。

        性能下降:由于缓存的失效和重新生成需要额外的计算和I/O操作,这可能导致性能下降,特别是在高并发环境中。

    3.2.2 内存消耗问题

    MySQL的查询缓存由query_cache_size来配置,限制了缓存池的大小,MySQL会根据需要动态分配内存来存储查询结果,但不会超过配置的缓存池大小。

    但是如果某个查询可能返回大量数据,它们会占用大量内存,此时将可能导致缓冲池容量不足,触发缓存淘汰策略,导致刚缓存下来的数据,又被淘汰掉。这可能导致系统不断进行分配和释放操作。

    此时一个比较好的办法,是增加query_cache_size的大小,但是查询缓存占用的内存资源可能会与其他数据库操作争夺系统内存资源,包括连接池、排序缓冲区、临时表等。内存资源的争夺可能导致系统性能下降,因为内存不足可能会导致数据写入临时文件,增加磁盘I/O开销。

     由于不好评估每次查询后,缓存下来数据的大小,有可能是1M,也可能是100M,导致系统不好设置query_cache_size大小,设置小了,在某些场景下,增加了维护缓存的开销,却获取不到缓存的优点。设置大了,又可能会导致内存资源的争夺,导致系统性能的下降。

    4. 查询缓存的逐渐弃用和移除

    从MySQL 5.7版本开始,MySQL默认情况下禁用了查询缓存。这意味着在MySQL 5.7及更高版本中,即使未明确禁用查询缓存,它也不会起作用。MySQL 5.7以后的版本采用了更现代的优化策略,因为查询缓存在高并发和动态数据环境下存在一系列问题,因此不再将其视为一种默认的性能优化手段。

    MySQL 8.0中彻底移除了查询缓存功能。这意味着在MySQL 8.0及更高版本中,查询缓存不再可用,无论是否尝试手动启用它,都将不起作用。

    5. 从查询缓存中得到的启发

    5.1 缓存设计时选择合适的粒度

    缓存粒度的设计是一个关键的决策,它直接影响到缓存的效率和命中率。合适的缓存粒度可以提高查询缓存的性能,而不合适的粒度可能会导致频繁的缓存失效或内存浪费。

    上面MySQL查询缓存的粒度太大了,是以整个查询语句为单位的,导致缓存的粒度太大,在数据频繁变化时,缓存会频繁失效,我们应该综合考虑,最终确定缓存的粒度。

    我们描述下缓存粒度时,可以考虑的两个点,可以在缓存设计时作为一些参考:

    要点 描述
    根据查询类型选择粒度 不同类型的查询可能需要不同的缓存粒度,根据查询的特性来选择合适的粒度。
    数据变化频率考虑 考虑数据的变化频率。如果数据变化频繁,

    5.1.1 根据需求选择缓存粒度

    首先是根据查询类型选择粒度,意思是根据查询的特性,我们可以选择在缓存中存储不同粒度的数据,以满足查询的需求。举个例子:

  • 查询特定产品信息:如果我们有一个查询是根据产品ID来检索特定产品的详细信息,这个查询可能只需要缓存该产品的详细数据,而不需要缓存其他产品的信息。这种情况下,缓存粒度较细,每个缓存对象对应一个产品的数据。
  • 查询销售报告:另一方面,如果我们有一个查询是生成销售报告,需要检索大量产品的销售数据并进行聚合,这个查询可能需要缓存更粗的粒度,例如整个报告结果,而不是单个产品的详细信息。这种情况下,缓存粒度较粗,每个缓存对象对应整个销售报告。
  • 所以,不同类型的查询对应着不同的数据需求和查询特性。根据每种查询的需求,我们可以选择合适的缓存粒度来提高性能和命中率。这个选择应该基于具体的应用程序场景和性能优化目标。

    5.1.2 数据变化频率

    第二个点是根据数据变化频率考虑,如果保存到缓存中的数据是频繁变化的,会导致缓存频繁失效,此时就应该考虑不将其缓存起来,亦或者使用更细粒度的缓存。如果存储在MySQL中的数据基本上不会变,此时即使缓存粒度大点,其实也是可以的,但是MySQL实际使用场景却不能一概而论,最终导致MySQL查询缓存的表现不那么理想。

    这里举个例子来说明, 如何根据数据变化频率来决定缓存的粒度。假设有一个服务提供股票信息的查询,为了提高查询性能,此时考虑引入缓存,将股票的信息缓存起来,降低系统的负载同时提高响应速度。

    股票的信息一般分为股票基本信息和价格两部分。其中股票的基本信息通常包括股票名称、代码、行业、市值等,这些信息相对稳定,不会经常变化。但是股票的价格是非常频繁变化的,通常以每秒甚至更快的速度更新。

    这里我们考虑缓存的粒度,如果选择的粒度是整个股票的信息,包含价格和股票的基本信息,将股票的所有信息都包含起来。此时会由于股票价格频繁变化,导致缓存频繁失效。相反,一个更好得做法是选择更细粒度的缓存,只缓存股票的基本信息,此时由于股票的基本信息变化频率非常低,缓存将不会频繁失效,这种策略可以提高缓存的命中率,

    因此,在设计缓存时,除了考虑实际的需求外,还需要考虑缓存下来数据的变化频率,如果某一部分数据相对于其他部分的内容,变化频率相对较高,此时比较好的做法是缩小缓存的粒度,能够更好得提高缓存的性能和命中率。

    5.2 性能监控

    在设计好缓存之后,我们应该通过监控关键指标,以及查看相关的日志记录,及时识别出缓存的设计合不合理,是否满足当前系统的要求。

    对于缓存,比较关键的指标,主要有缓存命中率,缓存失效率,内存使用情况,查询响应时间这几个指标。

    指标 描述 重要性
    缓存命中率 高命中率表明缓存有效减轻了数据库负载,提高了性能。低命中率可能表明缓存失效频繁或未满足查询需求。 非常重要
    缓存失效率 高失效率可能导致频繁的缓存失效,需要更好的缓存策略。低失效率表明缓存数据的稳定性。 非常重要
    内存使用情况 监控缓存所使用的内存量,确保它在可接受的范围内。过度消耗内存可能导致系统性能下降,并可能引发内存问题。 重要
    查询响应时间 跟踪响应时间有助于确保缓存不会引入不必要的延迟。较长的响应时间可能表明缓存失效或缓存访问速度较慢。 重要

    然后在实践过程中,应该选择合适的性能监控工具,用于监视关键性能指标。设置警报规则以便及时发现问题并采取行动。例如,如果缓存命中率下降到某个阈值以下,触发警报,缓存频繁失效,超过一定阈值后,也触发警报。

    通过这种自动化巡检工具,能够及时发现与查询缓存相关的性能问题,并采取适当的措施来改进系统的稳定性和性能。这个在整个缓存设计中也是至关重要的一环。

    6. 总结

    查询缓存是早期MySQL版本中用于提高查询性能的一项重要特性。然而,随着时间的推移,查询缓存逐渐显露出一些问题,如缓存粒度太大、内存消耗过多等。这些问题导致MySQL在5.7版本默认禁用查询缓存,并在8.0版本中完全移除了该功能。

    本文从MySQL查询缓存的引入初衷、工作原理,以及早期的流行和问题入手,详细讨论了查询缓存存在的核心问题。基于此,从中学习到一些缓存设计的经验。

    设计缓存时应考虑缓存粒度和数据变化频率,以提高性能和命中率。同时,性能监控在查询缓存中也是必不可少的,能够帮助我们及时识别查询缓存中存在的问题,并及时优化。

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论