buffer pool命中率低时,应优先调整innodb_old_blocks_pct和innodb_old_blocks_time参数,而非仅增大innodb_buffer_pool_size;尤其在存在全表扫描、大范围order by或周期性报表查询时,冷数据大量涌入并快速淘汰热数据,导致youngs/s偏低、pages read ahead偏高,需结合宿主机cpu调度延迟综合判断。

Buffer Pool 命中率低,光调 innodb_buffer_pool_size 很可能白忙——真正该动的是 innodb_old_blocks_pct 和 innodb_old_blocks_time,尤其当你有全表扫描、大范围 ORDER BY 或周期性报表查询时。
为什么命中率掉到 800 以下却 free_pages = 0?
这说明 Buffer Pool 里塞满了页,但大量是“刚进来就被淘汰”的冷数据。典型场景是后台定时任务执行 SELECT * FROM orders WHERE created_at > '2025-01-01' 这类无索引范围扫描:InnoDB 预读加载几百页,全扔进 Buffer Pool,但只访问一次就丢,直接把热数据挤出 Young 区。
此时查 SHOW ENGINE INNODB STATUS\G 的 BUFFER POOL AND MEMORY 段,会看到:
-
Pages read ahead明显偏高(比如 > 1000/秒) -
youngs/s极低(non-youngs/s 却很高 -
data_pages / total_pages接近 1.0,但命中率卡在 750 左右
怎么调 innodb_old_blocks_pct 才不翻车?
这个参数控制 LRU 链表中 Old 区占比,默认 37(即 37%)。它不是越大越好,也不是越小越灵,关键看你的负载类型:
- OLTP 主站(点查多、索引完善):保持默认 37 即可,改小反而让预读页过早进 Young 区
- 混合负载(白天点查 + 夜间报表):建议设为 25~30,缩小白名单式污染范围
- 以分析查询为主(如 BI 工具直连):可压到 15~20,但必须同步调高
innodb_old_blocks_time到 2000~3000 - 绝对不要设成 5 或 95 —— 前者导致预读失效严重,后者等于放弃分代保护
生效命令:SET GLOBAL innodb_old_blocks_pct = 25;(动态生效,无需重启)
innodb_old_blocks_time 设 1000 是硬伤?
默认 1000ms 意味着:一个页进 Old 区后,只要 1 秒内被再访问一次,就升入 Young 区。问题在于,很多报表查询的“二次访问”间隔远超 1 秒(比如 JOIN 后聚合、GROUP BY 排序耗时长),结果页还在 Old 区就被淘汰了。
实操建议:
- 先用
SELECT @@innodb_old_blocks_time;确认当前值 - 观察慢查询日志里典型分析 SQL 的执行时长,取 P95 值(比如 2300ms),则
innodb_old_blocks_time至少设为该值 + 500ms - 设太高(如 5000)会导致真正冷数据滞留过久,占着 Young 区位置;设太低(如 100)会让预读页“秒升”,污染加剧
- 推荐组合:
innodb_old_blocks_pct = 20+innodb_old_blocks_time = 2500
调完参数后必须盯住的三个指标
改完别以为万事大吉。接下来 24 小时要盯死这三项(每 5 分钟采样一次):
-
innodb_buffer_pool_hit_rate:目标是稳定 ≥ 950(即 95%+),且波动幅度 ≤ ±30 -
Pages made young/Pages not made young比值:理想区间 3:1 ~ 5:1;如果跌到 1:1,说明 Old 区太窄或 time 太短 -
innodb_buffer_pool_pages_free:不能长期为 0,也不能突然暴涨(> total × 0.2)——暴涨说明大量页因未被重访而快速淘汰,是配置过度保守的信号
最易被忽略的一点:容器环境(Docker/K8s)下,innodb_old_blocks_time 调高后,若宿主机 CPU 抢占严重,实际页访问延迟可能抖动更大,这时得结合 top -H -p $(pgrep mysqld) 看线程调度延迟,而非只信 MySQL 内部计时。











