全表扫描使buffer pool缓存被污染,因冷数据页大量加载并晋升young区挤占热页;调优innodb_old_blocks_pct(建议25)和innodb_old_blocks_time(建议2000ms)可抑制污染。

MySQL全表扫描为什么会让Buffer Pool缓存“变脏”
全表扫描会把大量冷数据页强行加载进Buffer Pool,挤走原本热点的热页——这不是缓存失效,是缓存被污染。InnoDB的LRU链表不是纯LRU,而是分了young和old两个子链表,但默认配置下,全表扫描读入的页会直接进入old区,如果访问频率够高(比如扫描过程中反复回表),它们很快就会晋升到young区,把真正该常驻的索引页、热点行页顶出去。
怎么调低全表扫描对LRU的干扰(innodb_old_blocks_pct和innodb_old_blocks_time)
这两个参数联手控制“新页是否容易晋升”,是防污染的核心开关:
-
innodb_old_blocks_pct默认37,表示old区占整个LRU链表的37%;值越小,old区越窄,新页越难在old区停留,但反而可能更快“误升”——一般不建议低于25 -
innodb_old_blocks_time默认1000(毫秒),意思是:一个页在old区里,必须被再次访问且间隔≥1秒,才允许晋升到young区;全表扫描中页被密集顺序访问,这个时间窗口能有效拦住它们晋升 - 实战建议:
SET GLOBAL innodb_old_blocks_time = 2000(拉长到2秒),配合innodb_old_blocks_pct = 25,对OLAP类扫描更友好
哪些场景下SELECT *或没走索引的查询最容易触发这个问题
不是所有全表扫描都一样危险,关键看访问模式和数据量:
- 小表(
-
SELECT * FROM orders WHERE status = 'pending'—— 如果status没索引,就是典型受害者;加个INDEX(status)比调参数管用十倍 - 备份工具(如
mysqldump --single-transaction)或ETL任务里的隐式全扫,容易被忽略,但实际压力巨大 - 注意
EXPLAIN输出里type: ALL和rows远大于filtered时,基本等于宣告要扫全表
别只盯着LRU调参:真正有效的缓存保护其实是绕开全表扫描
参数只是兜底,治本还得从查询设计入手:
- 给高频过滤字段补索引,哪怕只是
(status, created_at)这种联合索引,也能把ALL变成range - 用
SQL_NO_CACHE(MySQL 5.7)或SELECT /*+ NO_INDEX_MERGE() */(8.0)强制跳过Buffer Pool——仅限临时排查,不能当常规方案 - 对报表类大查询,考虑单独配一个
read_only从库,避免污染主库Buffer Pool - 监控
Innodb_buffer_pool_reads(物理读)突增 +Innodb_buffer_pool_read_requests(逻辑读)平稳,大概率就是缓存被扫崩了
真正麻烦的从来不是参数调不对,而是开发写完SELECT *从不看EXPLAIN,等慢查报警才想起查LRU——这时候热页早没了,调再久也没用。










