本节摘要:InnoDB 由缓冲池、change buffer、redo log、undo log、binlog 协同工作。缓冲池决定读得快不快,redo 保证崩溃不丢,undo 支撑回滚与多版本读。看懂这套器官系统,一半的"玄学性能问题"会变成可解释的生理现象。
磁盘读比内存慢几个数量级,InnoDB 把热点数据页缓存在内存的缓冲池里:
SHOW VARIABLES LIKE 'innodb_buffer_pool_size'; SHOW STATUS LIKE 'Innodb_buffer_pool_read%';
Innodb_buffer_pool_reads(穿透到磁盘的次数)除以 Innodb_buffer_pool_read_requests(总请求)就是未命中率。命中率长期低于 99%,说明内存不够装热点数据,这是加内存最无可争议的信号。
"重启后变慢、跑一会儿又快了"——这是缓冲池被清空后重新预热的现象,不是玄学。
binlog 在 Server 层,归档、复制用的都是它。一次 UPDATE 的提交要把 redo 与 binlog 都写盘,两阶段提交保证两本账对得上。
SHOW ENGINE INNODB STATUS\G -- 一页 InnoDB 体检报告
⚠️ 常见坑:长事务不提交,undo 无法清理,表空间像充气一样膨胀。看到 ibdata 或 undo 表空间疯涨,先查有没有挂了好几天的旧事务。
缓冲池不是一个大锅饭,InnoDB 把它分成新生代与老生代两个区域(类似缓存系统常见的分代设计)。新读入的页先进"新生代"浅尝辄止,被再次访问才有资格晋升"老生代"长期驻留。这个设计专治一个真实病症:一次性大扫描(比如月底全表跑一遍的对账 SQL)会把整个缓冲池的热数据冲刷出去,扫完之后全库的查询集体变慢——缓存污染。有了分代,一次性访问的页只污染新生代那一小块,核心热数据安然无恙。
-- 分代相关参数(默认新生代占 3/8,一般不必动) SHOW VARIABLES LIKE 'innodb_old_blocks_pct'; SHOW VARIABLES LIKE 'innodb_old_blocks_time';
实际调优中更常做的不是动分代参数,而是把"大扫描"从主库挪走:报表查询去从库、去分析引擎,让主库缓冲池专心服务 OLTP 热点。这又回到了第 1 章的选型哲学——很多参数问题,最后都是架构问题。
修改先落在缓冲池里的页上,这些页与磁盘不一致,就是脏页。脏页迟早要刷回磁盘,什么时候刷、刷多快,由 redo log 的剩余空间倒逼:redo 是环形复用的,写满之前必须把对应脏页刷下去腾地方。于是有一个反直觉现象——系统越空闲、突然一波写入高峰时,可能因为 redo 追平而出现刷盘抖动,表现为周期性的写入卡顿:
-- 观察脏页水位与刷盘活动 SHOW STATUS LIKE 'Innodb_buffer_pool_dirty'; SHOW STATUS LIKE 'Innodb_data_writes'; SHOW STATUS LIKE 'Innodb_redo_log_%';
6.3 节会把 innodb_redo_log_capacity 的容量规划讲透,这里先记住因果链:redo 容量小,写入高峰就容易被"该刷盘了"打断;容量给足,高峰期的写入可以攒着慢慢刷。
把几个器官串起来的最好方式是推演一次崩溃恢复。实例意外断电重启:先把 redo 里已提交未刷盘的重放(前滚),数据恢复到崩溃瞬间的已提交状态;未提交事务靠 undo 反向执行(回滚)。这个推演解释了两件日常事:为什么崩溃后重启要"恢复几分钟",那是在重放 redo,写入越猛重放越久,所以维护重启也要预留窗口;为什么提交了的数据绝不丢,redo 在 COMMIT 时已落盘,磁盘上的数据页旧一点没关系。
顺带一个容易混淆的辨析:redo 是 InnoDB 的物理日志,binlog 是 Server 层的逻辑日志。一个管崩溃恢复,一个管复制与归档,两本账由两阶段提交对齐。第 7 章备份恢复用的完全是 binlog 这本账,到时候回来对照。
change buffer 是容易被忽略的第四个器官。二级索引页不在缓冲池里时,插入操作不必立刻把页读进来——先把"这页将来要插入什么"记在 change buffer 里,等页被自然读到时再合并。这对写多读少的表(日志表、流水表)是显著减负:省掉大量为插入而做的随机读。
它有两个边界。唯一索引用不了:唯一性校验必须读页,缓冲没有意义,这也是"流水表索引别建唯一"的机理依据。写后立刻读的场景用不上:页马上要被读,缓冲白记还得合并,等于多一道手续。所以 change buffer 的受益场景画像非常清晰——大批量写入、查询集中在少数热页的表,比如按时间查最近数据的日志流水。
SHOW VARIABLES LIKE 'innodb_change_buffer%'; SHOW STATUS LIKE 'Innodb_buffer_pool_wait_free'; -- 缓冲池紧张的旁证