大查询会把热点数据刷出InnoDB Buffer Pool,因其加载大量非热点页时,按改进型LRU算法默认插入old sublist,若Buffer Pool已满,old sublist中久未访问的页(常为热点页)被优先淘汰;而innodb_old_blocks_time和innodb_old_blocks_pct可调控冷热分离,防止大查询页过早晋升young区。

为什么大查询会把热点数据刷出 InnoDB Buffer Pool
MySQL 的 InnoDB Buffer Pool 是有限的内存区域,用于缓存数据页和索引页。当执行一个扫描大量非热点页的大查询(比如全表扫描、未走索引的 WHERE 查询、或大范围 ORDER BY + LIMIT),InnoDB 会按需将这些新页载入 Buffer Pool。由于 LRU 算法实际是“改进型 LRU”(带 midpoint insertion strategy),新读入的页默认插入到 midpoint 之后(即“young 区”尾部附近),如果 Buffer Pool 已满,就会淘汰“old 区”里最久未访问的页——而这些往往正是被高频访问的热点数据页。
用 innodb_old_blocks_pct 和 innodb_old_blocks_time 控制冷热分离
这是最直接有效的配置级防护。InnoDB 默认把 Buffer Pool 分为 young 和 old 两段(比例由 innodb_old_blocks_pct 决定,默认 37%,即约 1/3 是 old 区)。新读入的页先放 old 区,只有在 innodb_old_blocks_time 毫秒内被再次访问,才会晋升到 young 区。大查询通常只扫一遍,因此其读入的页大概率停留在 old 区并被优先淘汰,不挤占热点页位置。
-
innodb_old_blocks_pct = 20:缩小 old 区,让真正冷的数据更快被淘汰(适合热点非常集中的场景) -
innodb_old_blocks_time = 1000(毫秒):要求页在 1 秒内被二次访问才晋升;太小(如 0)会让大查询页也轻易进入 young 区,失效保护;太大(如 5000)可能误伤部分真实低频但需保留的页 - 修改后需重启 MySQL 或动态 SET(5.7+ 支持
SET GLOBAL,但部分版本仅对新连接生效)
用 SQL_NO_CACHE 或 SELECT ... INTO DUMPFILE 避免污染查询缓存(仅限旧版)
注意:MySQL 8.0 已彻底移除查询缓存(query_cache_type 强制为 OFF),此条仅适用于仍在用 5.6/5.7 且开启 QC 的场景。即使如此,QC 对大查询几乎无益(结果集太大无法缓存),反而可能因频繁失效拖慢性能。更关键的是:SELECT SQL_NO_CACHE ... 能阻止该语句写入 QC,减少锁争用;而对纯粹想“绕过 Buffer Pool”的导出类操作,SELECT ... INTO DUMPFILE '/tmp/big.out' 会跳过结果集构造阶段,显著降低 Buffer Pool 压力——但它不返回结果,只写文件,适用场景受限。
应用层控制:拆分 + 覆盖索引 + 强制 FORCE INDEX
配置只是兜底,根本解法在 SQL 本身。大查询刷缓存的本质,是它读了不该读的页。常见对策:
- 对分页场景(如
OFFSET 1000000 LIMIT 20),改用基于游标的分页(WHERE id > ? ORDER BY id LIMIT 20),避免跳过百万行 - 确保
WHERE条件字段有覆盖索引,让查询只读索引页,不回表查聚簇索引(减少 50%+ Buffer Pool 占用) - 对明确知道走错索引的语句,加
FORCE INDEX (idx_name),防止优化器选中全表扫描路径 - 后台任务类大查询,尽量安排在业务低峰期,并限制并发数(如用应用层信号量或数据库
max_execution_time)
Buffer Pool 的冷热混杂问题不会完全消失,但只要大查询不反复触发全表扫描、且 old 区策略得当,热点页就能稳在 young 区——这点比盲目加大 Buffer Pool 更有效。











