MySQL查询缓存和缓冲池机制

聊 MySQL 缓存,很多人脑子里只有查询缓存,但它在 MySQL 8.0 里已经被彻底删掉了。真正扛性能的是 InnoDB 缓冲池(Buffer Pool)。查询缓存为什么被废、缓冲池是怎么工作的

聊到 MySQL 的缓存,很多人第一反应是「查询缓存」(Query Cache)——同一条 SQL 查过一次,结果存起来,下次直接返回。

听着很美,但有个坑:这个功能在 MySQL 8.0 里已经被彻底删掉了。

所以如果你还抱着「开个查询缓存就能提速」的想法,那是老黄历了。

MySQL 几层缓存机制,重点说清查询缓存为啥被废、现在真正该关注哪层缓存。

MySQL 的缓存不止一个

说「MySQL 缓存」太笼统。它内部其实有好几层缓存,作用完全不同:

  1. 查询缓存(Query Cache):缓存 SQL 和它的结果(已废弃)
  2. InnoDB 缓冲池(Buffer Pool):缓存数据页和索引页(真正的性能关键)
  3. 其他小缓存:比如表定义缓存、线程缓存等

大家最常误解的是把第 1 个当成 MySQL 缓存的全部,而真正扛性能的是第 2 个。咱们分开说。

查询缓存为什么被废了

查询缓存的思路很直白:把一条 SELECT 语句当 key,查询结果当 value,存在内存里。下次来一条一模一样的 SQL,直接吐结果,不用再查。

理想很丰满,现实很骨感。它有几个致命问题:

命中条件极其苛刻:两条 SQL 必须一个字节都不差才算同一个 key。多一个空格、大小写不同、带了个 NOW(),就不命中。

失效太频繁:只要一张表有任何写操作(INSERT/UPDATE/DELETE),这张表相关的所有查询缓存全部作废。写多读少的表基本等于没缓存。

维护本身有开销:每次查询都要去缓存里查一遍、每次写都要去清理,这个加锁和维护的成本,在高并发下反而拖慢整体。

结果就是:在真实的读写混合业务里,查询缓存经常是负优化——省下的时间还不够它自己折腾的。

所以 MySQL 5.7 就把它默认关了,到 8.0 直接整个功能删除。

在 5.7 及以前,可以这样看它开没开:

-- have_query_cache=YES 表示支持,query_cache_type=OFF 表示没启用
SHOW VARIABLES LIKE 'query_cache%';
​

一句话结论:别指望查询缓存,新版本根本没有,老版本也建议关掉。

真正扛性能的:Buffer Pool

查询缓存走了,但 MySQL(InnoDB 引擎)真正的性能命脉是另一个东西——缓冲池(Buffer Pool)。

它缓存的不是「SQL 的结果」,而是「数据本身」。

Buffer Pool 是什么

InnoDB 把磁盘上的数据组织成固定大小的「页」(默认 16KB)。每次读数据,都是以页为单位从磁盘加载进内存。

Buffer Pool 就是内存里专门用来存这些页的区域。

读数据时:先查 Buffer Pool,页在内存里就直接用,不在才去磁盘读(顺便把这页缓存进来)。

写数据时:先改 Buffer Pool 里的页,不立即刷盘,由后台线程择机异步刷(这是 InnoDB 能做到高写入吞吐的关键之一)。

Buffer Pool 有多重要

一次磁盘 I/O 的耗时大概是内存访问的几百到几千倍。Buffer Pool 命中率越高,磁盘 I/O 越少,性能越好。

生产环境一般要求 Buffer Pool 命中率在 99% 以上,低于 95% 就得考虑加内存或优化查询了。

查命中率:

SHOW STATUS LIKE 'Innodb_buffer_pool_read%';
​

关键指标:

  • Innodb_buffer_pool_reads:从磁盘读的次数(没命中)
  • Innodb_buffer_pool_read_requests:总读请求次数

命中率 = (read_requests - reads) / read_requests * 100%

Buffer Pool 大小怎么配

默认只有 128MB,生产环境完全不够用。

通常建议配到物理内存的 50%~75%,留一部分给操作系统和其他进程。

-- 查看当前配置
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
​

在 my.cnf 里配置(重启生效):

[mysqld]
# 比如 16GB 内存的机器,给 10GB
innodb_buffer_pool_size = 10G
​

MySQL 5.7.5 之后支持在线动态调整,不用重启:

SET GLOBAL innodb_buffer_pool_size = 10737418240; -- 10GB,单位字节
​

Buffer Pool 的淘汰机制

Buffer Pool 容量有限,装满了新页进来,就得淘汰旧页。

InnoDB 用的是改良版 LRU(最近最少使用)算法:把 LRU 链表分成**热区(young)和冷区(old)**两段,新读进来的页先进冷区,在冷区待够一段时间(默认 1 秒)之后再被访问,才会晋升到热区。

这个设计是为了防止全表扫描把 Buffer Pool 全冲刷掉——全表扫描会把大量页读进来,但这些页只用一次,不应该把真正的热数据挤走。

多个 Buffer Pool 实例

高并发场景下,单个 Buffer Pool 会成为锁竞争的热点。

可以配置多个实例,每个实例独立管理,减少锁争用:

[mysqld]
innodb_buffer_pool_size = 10G
# 配 8 个实例,每个约 1.25GB
innodb_buffer_pool_instances = 8
​

实例数建议和 CPU 核心数对应,或者按每个实例 1GB 以上来估算。

两者的差异对比

查询缓存 Buffer Pool
缓存对象 SQL 查询结果 数据页、索引页
失效触发 表有任何写操作 LRU 淘汰
适用引擎 所有引擎 InnoDB 专属
当前状态 8.0 已删除 核心机制,一直在
配置方向 不建议开 尽量配大

小结

  • 查询缓存:设计有缺陷,失效太频繁,MySQL 8.0 已删除,不用管它了
  • Buffer Pool:InnoDB 的核心,缓存数据页和索引页,直接决定磁盘 I/O 频率
  • 生产环境 Buffer Pool 配到物理内存的 50%~75%,命中率要盯着 99% 以上
  • 高并发场景开多个 Buffer Pool 实例,减少锁竞争

真正影响 MySQL 读性能的,是 Buffer Pool 够不够大、索引建得对不对,而不是查询缓存。

更多推荐

章节目录