MySQL查询缓存和缓冲池机制
聊 MySQL 缓存,很多人脑子里只有查询缓存,但它在 MySQL 8.0 里已经被彻底删掉了。真正扛性能的是 InnoDB 缓冲池(Buffer Pool)。查询缓存为什么被废、缓冲池是怎么工作的
聊到 MySQL 的缓存,很多人第一反应是「查询缓存」(Query Cache)——同一条 SQL 查过一次,结果存起来,下次直接返回。
听着很美,但有个坑:这个功能在 MySQL 8.0 里已经被彻底删掉了。
所以如果你还抱着「开个查询缓存就能提速」的想法,那是老黄历了。
MySQL 几层缓存机制,重点说清查询缓存为啥被废、现在真正该关注哪层缓存。
MySQL 的缓存不止一个
说「MySQL 缓存」太笼统。它内部其实有好几层缓存,作用完全不同:
- 查询缓存(Query Cache):缓存 SQL 和它的结果(已废弃)
- InnoDB 缓冲池(Buffer Pool):缓存数据页和索引页(真正的性能关键)
- 其他小缓存:比如表定义缓存、线程缓存等
大家最常误解的是把第 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 够不够大、索引建得对不对,而不是查询缓存。
