当前位置:首页 > 问答 > 正文

MySQL查询缓存到底咋回事,原理和细节讲得挺透彻的分享

主要综合了早期MySQL官方文档、高性能MySQL书籍、以及像姜承尧、何登成等数据库领域专家的技术博客和分享中的核心观点)

说起MySQL的查询缓存,这玩意儿在MySQL 5.7及更早的版本里,是个让人又爱又恨的功能,爱它是因为,当它真的起作用时,效果立竿见影,能把一个复杂的查询速度提升几个数量级,恨它是因为,它太容易成为性能瓶颈,而且管理起来非常麻烦,所以在MySQL 8.0里直接被官方给移除了,但理解它为啥被移除,对我们怎么写好SQL语句、怎么设计数据库,非常有帮助。

查询缓存是个啥?简单比喻

你可以把它想象成一个超级简单的“字典”或者“备忘录”,每当MySQL收到一条SELECT语句,它不会立刻去翻书查数据,而是先看看这个“备忘录”。

它的工作流程大概是这样的:

MySQL查询缓存到底咋回事,原理和细节讲得挺透彻的分享

  1. 拿到一条SELECT语句。
  2. 对这条语句进行一个哈希计算,生成一个唯一的“指纹”(也就是哈希值),注意,这里哪怕你的SQL语句只是大小写不同,或者多个空格,生成的“指纹”都会不一样,就会被当成两条不同的查询。
  3. 拿着这个“指纹”去“备忘录”(查询缓存)里找,看有没有之前记录下来的结果。
  4. 如果找到了,并且有权限访问这些数据,那就太好了!直接把这个“标准答案”返回给客户端,根本不用去麻烦存储引擎(比如InnoDB)去磁盘上找数据了,这一步叫做“缓存命中”。
  5. 如果没找到,那就只能老老实实地去执行查询,等拿到结果后,如果满足条件,MySQL会把这个结果和当初的查询“指纹”一起,存到“备忘录”里,方便下次再用。

查询缓存工作的关键细节和“苛刻”条件

为啥说它“苛刻”呢?因为它判断两个查询是否相同的规则非常死板。

  • 一字不差:查询语句必须完全一样,包括大小写、空格、注释。SELECT * FROM tselect * from t 会被认为是两条不同的查询,不会共用缓存。
  • 不能有不确定函数:查询里如果包含了像 NOW()CURRENT_DATE()RAND() 这样的函数,因为每次执行结果都可能不同,所以这条查询的结果是不会被缓存的,同理,用了用户自定义变量或者系统变量的查询,也基本不会被缓存。
  • 查询涉及的表必须有读取权限:这个好理解,你都不能读这个表,自然不能缓存它的结果。

缓存何时失效?这是最大的痛点!

这才是查询缓存最核心也最让人头疼的地方,它的失效策略非常“粗粒度”。

MySQL查询缓存到底咋回事,原理和细节讲得挺透彻的分享

想象一下,你的“备忘录”里记录了很多问题的答案,问题的源头数据发生了一点点变化,比如你只是往用户表里插入了一条新用户记录,或者修改了某条商品的价格,这时会发生什么?

MySQL的处理方式是:把这个用户表相关的所有“备忘录”条目,全部撕掉!

是的,你没看错,是全部,不管你的缓存结果是来自于一个查询一亿条数据的复杂报表SQL,还是一个只是简单查询表里有多少行数据的 SELECT COUNT(*),只要这个表发生了任何写操作(INSERT、UPDATE、DELETE、TRUNCATE,甚至某些ALTER TABLE),那么所有和这个表有一丁点关系的查询缓存,都会被清空。

这就带来了几个严重问题:

MySQL查询缓存到底咋回事,原理和细节讲得挺透彻的分享

  1. 写操作成本剧增:一个本来很快的UPDATE语句,除了要更新数据,还要额外承担清理庞大查询缓存的工作,在高并发的写环境下,这会给数据库带来巨大的压力,经常出现的情况是,你辛辛苦苦把结果缓存起来,还没被读几次,就被一个写请求给清空了,缓存命中率极低,反而得不偿失。
  2. “锁”竞争:MySQL需要保证在查询缓存的时候,不会有其他线程来修改缓存的内容,所以它有一个查询缓存锁,当有大量并发查询和写入时,这个锁会成为激烈的争用点,导致系统瓶颈,姜承尧在他的文章里多次提到,在高并发场景下,查询缓存的内置锁竞争是导致性能急剧下降的主要原因之一。

为啥最终被MySQL 8.0抛弃了?

正是因为上述这些固有的缺陷,尤其是对写性能的拖累和粗粒度的失效机制,使得查询缓存在现代高并发、高写入的应用中弊大于利,官方团队认为,与其保留一个效率不高且难以优化的功能,不如直接移除,从而简化代码结构,减少潜在的锁竞争,让用户去寻求更优的解决方案。

给我们的启示

虽然查询缓存本身已经“退役”了,但它的兴衰给了我们很多宝贵的经验:

  • 不要过度依赖数据库层面的透明缓存:对于复杂的、读多写少的热点数据,更好的办法是在应用层(比如使用Redis、Memcached)做一层独立的缓存,这样缓存的生命周期可以由应用逻辑精确控制,失效策略更灵活,也不会影响数据库本身的写入性能。
  • SQL语句规范化很重要:即使没有查询缓存,养成编写统一、规范SQL的习惯(比如统一大小写、空格),也有利于应用层缓存和数据库本身对重复查询的优化。
  • 理解“任何设计都有权衡”:查询缓存的初衷是好的,但它的实现没有跟上时代的发展,这提醒我们,在选择技术方案时,一定要结合自己的业务场景(是读多写少还是写多读少?并发量多大?),充分了解其优缺点,而不是盲目开启默认配置。

MySQL查询缓存是一个特定历史时期下的产物,它简单直接地解决了“重复读”的问题,但其僵化的机制无法适应复杂的现代应用场景,它的故事是一个关于技术权衡和演进的经典案例。