innodb的lru链表分为young和old子链表,全表扫描页插入old区头部且需停留≥innodb_old_blocks_time(默认1000ms)并被再次访问才晋升,因顺序访问间隔短故基本不晋升;调大该参数比调小innodb_old_blocks_pct更有效,因延长观察期可硬性拦截冷页晋升,避免热页被挤出。

全表扫描的数据页为什么不会直接冲掉热数据
因为InnoDB的LRU链表不是纯LRU,而是分成了young和old两个子链表。新读入的数据页默认插入old子链表头部,而不是直接进young区——这一步就卡住了污染源头。
常见错误现象:调了innodb_buffer_pool_size却还是发现慢查变多、Innodb_buffer_pool_reads突增,其实是没意识到冷页正在悄悄晋升。
- 全表扫描读入的页,首次访问后仍留在
old区,除非满足“在old区停留≥innodb_old_blocks_time毫秒后再次被访问” - 默认
innodb_old_blocks_time = 1000,但全表扫描中页被密集顺序访问,间隔远小于1秒,所以基本不晋升 -
innodb_old_blocks_pct = 37表示old区占整个LRU链表的37%,这个比例决定了冷页能“蹲坑”的空间大小
为什么调大innodb_old_blocks_time比调小innodb_old_blocks_pct更有效
拉长冷页的“观察期”,比缩窄冷区空间更能稳住热数据。因为old区太窄(比如设成10)反而会让新页更容易因链表重排而误升到young区;而加时间阈值是硬性拦截,逻辑更干净。
使用场景:OLAP类报表查询、ETL抽取、mysqldump --single-transaction备份任务。
- 建议设为
innodb_old_blocks_time = 2000(2秒),配合innodb_old_blocks_pct = 25 - 该配置对物理读压力大的场景更友好,但注意:它只影响新页晋升,不改变已有热页位置
- 修改后需执行
SET GLOBAL innodb_old_blocks_time = 2000,无需重启,但仅对后续新加载页生效
EXPLAIN里看到type: ALL时,LRU参数只是最后一道防线
真正让Buffer Pool扛住压力的,从来不是参数,而是有没有走索引。当EXPLAIN输出显示type: ALL且rows远大于filtered,说明MySQL正准备把整张表拖进内存——这时候调参只是延缓崩溃,不是解决问题。
容易踩的坑:开发写完SELECT *从不看执行计划,等监控报警才想起查innodb_old_blocks_time,此时热页早已被挤出,调再久也救不回命中率。
- 高频过滤字段补索引(如
status、created_at)比任何LRU调优都管用 - 对无法加索引的模糊查询(如
LIKE "%xxx%"),考虑改用FULLTEXT或外部搜索引擎 - 报表类大查询尽量路由到
read_only从库,避免污染主库Buffer Pool
监控时重点盯哪几个指标才抓得住污染迹象
单看Innodb_buffer_pool_read_requests平稳、Innodb_buffer_pool_reads飙升,就是最典型的污染信号——逻辑读没变,物理读暴增,说明热页丢了,不得不反复回磁盘。
性能影响:这类问题不会立刻OOM,但会显著抬高P99延迟,尤其在高峰期叠加多个全扫时,业务查询可能集体抖动。
- 持续关注
Innodb_buffer_pool_read_requests / Innodb_buffer_pool_reads比值,跌破50就要警觉 -
SHOW ENGINE INNODB STATUS里的BUFFER POOL AND MEMORY段可查看young/old区实际占比 - 注意
mysqladmin extended -r -i 1 | grep -i "buffer_pool_read"这种实时流式监控,比定时采样更早发现问题
old区里待够了时间、又被恰好点中——那一次访问,就足以让它滑进young区,顶走你正在高频查询的二级索引页。











