Buffer Pool未命中时触发同步物理读,线程阻塞直至16KB整页从磁盘加载完成并校验通过后才继续执行,不支持按行读取或降级处理。

Buffer Pool未命中时触发的是同步物理读,不是异步
很多人误以为 buf_page_get_gen() 未命中时会“悄悄发起异步 I/O”,实际它走的是同步路径:查询线程会被阻塞,直到整页(16KB)从磁盘加载完成并放入 Buffer Pool 后才继续执行。异步 I/O 只用于预读(read_ahead)或后台刷脏页,不用于单次页请求。
关键点在于:fil_io() 在该上下文中以同步模式调用,操作系统级 read() 或 pread() 返回后,页才被解析、校验、链入 LRU list。这意味着一次未命中可能直接拖慢整个查询响应时间——尤其是当磁盘延迟高(如机械盘或高负载 NVMe)时。
每次只读16KB整页,哪怕只查一行
InnoDB 不支持“按行读取”或“部分页加载”。只要目标行所在页不在 Buffer Pool 中,就必须把整个数据页(默认 16KB)从磁盘拉进来。这个页里可能包含几十甚至上百行无关数据,但 MySQL 没得选。
- 二级索引查询还要额外回表:先读索引页 → 再根据主键定位聚簇索引页 → 又一次 16KB 物理读
- 全表扫描时虽有区(
extent)级预读,但实际载入内存仍以单页为单位,且每页都走一遍相同流程 -
LIMIT 1无法跳过后续页加载:若满足条件的行在第100页,前99页仍可能被顺序读入(取决于是否触发预读)
free 链表决定页往哪放,LRU list 决定淘汰谁
页加载前,InnoDB 先从 free 链表头部取一个空闲缓冲页描述块(buf_page_t),把磁盘页内容拷贝进去,并填入 space_id 和 page_no 等元信息;之后才将该页节点插入 LRU list 的 young 区头部。
这里容易混淆的是:free 链表只管“有没有空位”,不管“放哪儿更合适”;而真正影响后续缓存效率的是 LRU 分区策略:
- 刚加载的页进
young区,避免被全表扫描污染 - 如果页在
old区被访问,需满足一定条件(如间隔超innodb_old_blocks_time)才晋升到young区 - 淘汰发生在
old区尾部,不碰young区热数据
checksum 失败会直接中止查询,不降级或重试
页从磁盘读入后,InnoDB 立即校验页头的 checksum 值。一旦校验失败(常见于磁盘坏道、RAID掉盘、静默数据损坏),MySQL 不会尝试修复、跳过或返回部分数据,而是直接报错:
ERROR 1034 (HY000): Incorrect key file for table 'xxx'; try to repair it
或更底层的:
ERROR 2013 (HY000): Lost connection to MySQL server during query
这种失败不可绕过,必须人工介入(如从备份恢复、运行 mysqlcheck --repair)。监控 Innodb_buffer_pool_reads 上升同时伴随 Innodb_data_fsyncs 异常,往往是存储层开始出问题的早期信号。


















