ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

MySQL性能优化实战:InnoDB缓冲池、Redo Log与崩溃恢复机制深度解析

MySQL性能优化实战:InnoDB缓冲池、Redo Log与崩溃恢复机制深度解析

MySQL 优化做了几年,我最大的感受是:调参抄作业解决不了根子上的问题。你知道innodb_buffer_pool_size该调大,但不知道为什么调大,也不知道调大之后瓶颈会转移到哪里。这一篇是专栏第六章,我准备把 InnoDB 底层那套机制彻底拆开讲——存储结构、缓冲池、redo log、undo log、binlog 两阶段提交、崩溃恢复,全串起来。适合正在系统啃 MySQL 性能调优的开发者、要接手生产库的运维同学,以及所有被"参数调了一圈性能还是没上去"折磨过的人。搞清楚这些原理之后,你再去看慢日志和监控指标,会发现很多问题不用猜,直接能推出来。

1. InnoDB 底层存储模型:页、区、段与 B+ 树的物理逻辑

1.1 存储引擎层为什么最值得你花时间

好多人在 MySQL 性能调优这条路上跑了很久,还是被一个问题卡住:"为什么同一个 SQL,在某些环境里飞快,换一台机器就慢成狗?" 答案经常不在 SQL 本身,而在 InnoDB 这一层。

MySQL 整体是分层的:最上面是连接管理和 SQL 接口,再往下是解析器、优化器这些服务层组件,最底下才是存储引擎层。服务层管的是"这个 SQL 该怎么执行",而 InnoDB 管的是"数据到底存在哪、怎么读、怎么保证不丢"。慢查询优化做得再漂亮,存储引擎每次都要从磁盘随机 I/O 里捞一条记录,整体上限就被卡死了。

InnoDB 成为默认引擎不是没有理由的。它支持事务、行级锁、MVCC 多版本控制、崩溃恢复、外键约束,这些能力在业务系统里几乎是刚需。但反过来讲,这些能力全都是有成本的——行锁要维护锁结构,MVCC 要保留历史版本,崩溃恢复要写日志,这每一笔开销最后都会体现在性能指标上。你只有搞清楚它内部怎么组织数据、怎么用内存、怎么刷磁盘,才有资格谈调优。

1.2 页、区、段、表空间:磁盘上的数据到底怎么摆

InnoDB 在磁盘上的管理单位不是行,而是页(Page),默认大小是 16KB。页再往上组合,是区(Extent),一个区正好是 1MB,等于 64 个连续的页。区再往上,是段(Segment),段按用途又分成数据段、索引段、回滚段。一个 InnoDB 表的数据和索引,默认放在独立表空间(也就是那个.ibd文件)里,由innodb_file_per_table控制,这个参数在 8.0 里默认就是 ON。

单个页里面是什么结构?大致有这么几块:

  • 文件头:记录这个页的编号、所在表空间、上一页下一页的指针,还有最近一次修改这个页的 LSN(日志序列号)。
  • 页目录:里面存的是页内记录的位置槽,方便在页内做二分查找。
  • 行记录区域:真正存放业务数据的地方,每一行还有隐藏字段,包括row_id、trx_id(最后修改事务 ID)和roll_ptr(回滚指针,指向 undo log 里的旧版本)。

页大小为什么默认偏偏是 16KB?这个数字是取舍过的。页太小,B+ 树层级变多,范围查询一次要读的页数也多,磁盘 I/O 次数直线上升;页太大也不行,单行更新的时候一页里只改几字节也要整页刷出去,浪费 I/O,而且缓冲池里能缓存的页数量就少了,内存利用率反而下降。所以 MySQL 默认用了 16KB 这个比较均衡的值。MySQL 也支持KEY_BLOCK_SIZE或者压缩行格式,把页压成 8KB、4KB,但这会额外消耗 CPU,适合那种行特别宽、随机读少、扫描多的场景,日常业务里不要随便开。

顺带说一个特别实用的估算:以 16KB 页、主键 8 字节加指针约 6 字节为例,一个非叶子页大约能存 1170 个目录项。叶子节点如果按每行 1KB 算,一页放 16 行。三层 B+ 树大约能扛 2190 万行数据。这个估算在调优时非常有用——你发现表超过千万级后查询明显变慢,很可能就是 B+ 树从第三层跨到了第四层,每次查询多了一次磁盘 I/O。这时候不仅是加索引的问题,主键设计太长、行太宽,都会让单页能容纳的记录数变少,层数提前加深。

2. Buffer Pool 缓冲池:内存调优的核心切入点

2.1 缓冲池模型:内存与磁盘之间那块最关键的地带

InnoDB 读数据是不直接读磁盘的。查询走到存储引擎,先去内存里找页;找不到,才把磁盘上的页读进内存,再返回结果。这块内存区域就是 Buffer Pool,它的大小和机制,决定了 InnoDB 到底能帮你扛住多大的读压力。

Buffer Pool 内部是一个巨大的页链表结构。为了高效管理,InnoDB 维护了几条链:

  • Free List:空闲页链表,里面挂的是还没被使用的页。
  • LRU List:正在使用的页,按访问频率排列。
  • Flush List:脏页链表,按最早修改时间排序,标记了哪些页需要刷回磁盘。

一个读请求进来,先去 LRU List 里找页;找不到就去 Free List 拿一个空闲页,把磁盘数据读进去,挂到 LRU 上;如果 Free List 已经空了,就按 LRU 算法淘汰一个冷门页,腾出位置。一个写请求进来,先在 Buffer Pool 里改对应的页,这个页就变成了脏页,挂到 Flush List 上,之后再由后台线程刷回磁盘。

理解这个模型之后,很多参数就好懂了。比如innodb_buffer_pool_instances,把缓冲池拆成多个实例,每个实例维护自己的链表,这样并发访问的时候,不用所有人都去抢同一把锁。这个参数在 Buffer Pool 大于 1GB 时建议调整为 8 左右,太小了起不到降低锁竞争的作用,太大了反而增加管理开销。

2.2 缓冲池容量规划与命中率观测

Buffer Pool 大小直接决定了热点数据能装多少。默认值只有 128MB,生产库如果还是这个值,性能基本全靠磁盘扛,那调优就无从说起。我一般推荐把 Buffer Pool 配置到物理内存的 60% 到 75% 之间。注意这是个范围,具体得看你机器内存总量、实例数量、是否和其他中间件共用机器。比如 64GB 内存的专用数据库机,配 40GB 到 48GB 比较常见;如果同一台机器还跑了别的服务,就要保守一点,留足给操作系统和文件缓存的空间。

怎么判断大小合不合适?两个角度。第一看命中率,执行SHOW ENGINE INNODB STATUS\G,里面有一行Buffer pool hit rate,这个值通常要保持在 0.99 以上,低于 0.95 就要警惕了——意味着相当一部分读请求穿透到了磁盘。第二看 Free List 是否经常为空,如果频繁出现没有空闲页、需要立刻淘汰的情况,说明缓存空间不够用了。

但注意,命中率低不一定就是 Buffer Pool 太小。排查顺序应该是:先用慢日志和全表扫描记录确认业务侧有没有大查询扫全表,再决定要不要扩容。我见过不少案例,Buffer Pool 从 8GB 加到 32GB,命中率没怎么变,因为罪魁祸首是某个定时任务每天拉全表扫一遍,把热点页全部冲掉了。内存加了等于白加,后面这个压测案例我还会展开。

2.3 冷热分区 LRU:防止全表扫描冲掉热点数据

这里要讲一个 InnoDB 里特别容易被忽视的设计——LRU 冷热分区。标准 LRU 有个问题:如果一个从来没被访问过的冷页被读进来,它也会被放到 LRU 链表的头部,看起来像热点页。全表扫描就是典型的冷数据批量侵入,如果不做特殊处理,这些页会把真正的热点页从缓存里挤出去。

InnoDB 的解法是把 LRU 链表分成两段:前 5/8 是 young 区(热区),后 3/8 是 old 区(冷区)。新读入的页先挂在 old 区头部,如果在innodb_old_blocks_time毫秒内再次被访问,才有资格升入 young 区;否则就在 old 区里慢慢沉底被淘汰。

这个设计操作用处很大。innodb_old_blocks_time默认是 1000 毫秒,我建议对 OLTP 业务不要低于这个值。比如你把它设成 0,就等于关掉了保护机制,全表扫描的页一进来就直接窜进热区,热点数据很快会被冲光。之前遇到过一个业务,每天凌晨跑批任务把缓存搞废,白天高峰期全部穿透磁盘,后来把innodb_old_blocks_time从默认值调到 2000,跑批任务对缓冲池的污染立刻小了很多。

另外还有个参数innodb_old_blocks_pct,默认 37,控制 old 区占整个 LRU 的比例。这个值一般不需要动,除非你确认自己的业务经常有大批量扫描,可以稍微调大,给冷区更多缓冲空间。

3. Redo Log 与 Undo Log:日志机制是怎么撑起崩溃恢复和 MVCC 的

3.1 WAL:为什么先写日志再落盘反而更快

InnoDB 的性能底线来自一个看起来很反直觉的策略:先更新内存页,再写磁盘日志,最后才是真正刷脏页。这就是 WAL(Write-Ahead Logging)机制的核心——数据页的变更先记日志,日志落盘之后,内存页的修改才算"安全"。

为什么这样做反而快?你自己想象一个场景:一条 UPDATE 改了 100 行,这 100 行分散在 50 个页里。如果不做 WAL,每次提交都要把这 50 个页全部刷到磁盘,每一页都是随机写,磁盘就废了。有了 redo log 之后,提交时只需要把这次变更的物理信息顺序写入日志文件。顺序写比随机写快一到两个数量级,这是 WAL 的根本优势。

redo log 本质上是物理日志,记录的是"某个页在某个偏移量上被改成了什么"。它不是记录 SQL 语句,而是记录对页的操作结果。所以崩溃恢复时,InnoDB 不需要重放 SQL,只需要按日志把对应的页重新修一遍,效率极高。

日志先落盘,数据页后落盘,那如果中途宕机了,内存里的丢失了怎么办?没关系,因为决定事务是否提交的关键数据——redo log——已经在磁盘上了。恢复时重新回放日志,就把内存页丢失的修改补回来了。这就是"日志先行"的安全感。

3.2 Redo Log 的环形空间、LSN 与刷盘策略

redo log 不是无限增长的,它是一组固定大小的文件,写满了就回头覆盖最旧的部分。这个环形空间里,有两个关键位置:一个是写入位点,代表新日志写到哪了;另一个是 checkpoint 位点,代表哪些日志对应的脏页已经刷回磁盘、可以安全覆盖了。两者之间的区域,就是"还有效的 redo log 空间"。

每个日志记录都有一个 LSN(Log Sequence Number),单调递增。每次刷盘,Log flushed up to这个值往前走;每次做 checkpoint,Last checkpoint at也往前走。SHOW ENGINE INNODB STATUS\G里面能看到这些指标,它们之间的差距,就代表当前系统里"未完成落盘的工作量"。

redo log 文件太小会有什么后果?日志写满但 checkpoint 跟不上,InnoDB 就会强迫把脏页刷盘推进 checkpoint,腾出覆盖空间。刷盘过程中,事务提交会被卡住,表现为"日志等待、请求响应变慢"。这种问题非常隐蔽,不是磁盘满了,也不是 CPU 不够,就是 log 文件大小和业务写入速率不匹配。

刷盘时机由innodb_flush_log_at_trx_commit控制,这个参数有三个值,理解它比背下来更重要:

取值行为崩溃丢失窗口
0事务提交时不写日志,由后台线程每秒刷一次最多丢 1 秒更新
1每次事务提交都把日志刷到磁盘不丢失
2每次提交写入操作系统缓存,每秒再刷磁盘依赖 OS,最多丢 1 秒

innodb_flush_log_at_trx_commit=1是常规业务下最稳的配置,但代价是每次提交都有一次 fsync,吞吐量受磁盘能力限制。这时候就轮到 group commit 登场了——多个事务在提交阶段会凑在一起,合并成一次 fsync,这样既保证了安全性,又摊薄了刷盘开销。生产环境我基本不会把刷盘策略降到 0 或 2,除非业务能明确接受丢数据的风险,比如纯日志、缓存类数据可以重建的场景。

对于 8.0.30 之后的版本,redo log 大小不再直接配innodb_log_file_size,而是用innodb_redo_log_capacity,默认好像 100MB,生产至少调到 1GB 以上,写密集的业务直接给 2GB 到 4GB。你可以查阅当前版本的默认值,原则是:让 redo log 能覆盖一个业务高峰期的写入总量,别让 checkpoint 频繁前移。

3.3 Undo Log 与 MVCC:版本链到底是怎么串起来的

redo log 解决的是"改了能不能不丢"的问题,而 undo log 解决的是"改了能不能反悔、能不能看到旧快照"的问题。

Undo log 记录的是逻辑上相反的操作:INSERT 对应记录的删除标记,DELETE 反过来是重新插入,UPDATE 则记录更新前的旧值。它的用途有两个,一是事务回滚时恢复数据,二是 MVCC 里给读操作提供历史版本。

InnoDB 的每个数据行上都有一个隐藏字段roll_ptr,指向该行在 undo log 里的上一次修改记录。每当一个事务修改这行数据,就会在 undo log 里写入旧版本,并把roll_ptr指向那个旧版本,同时把trx_id改成当前事务的 ID。这样一行数据从新到旧,通过roll_ptr串成一条版本链。

MVCC 查询的时候,InnoDB 会根据当前事务的 Read View 判断版本可见性:沿版本链往后找,找到第一条"在我这个事务开始之前已经提交"的版本,就返回那个版本的数据。这就是为什么长事务会拖垮性能——一个事务长期不提交,它创建 Read View 之后的所有新版本它都看不见,旧版本数据就不能被清理,undo log 不断膨胀,版本链越拉越长,读操作沿着版本链要翻很多页才能找到该看的版本。

监控里要看的一个数是History list length,它在SHOW ENGINE INNODB STATUS\G的事务段里。这个值代表未被 purge 线程清理的旧版本数量。正常情况下几千到几万都算健康,如果涨到几十万甚至上百万,基本可以断定业务里有长事务或者大事务卡着没提交。我常见的情况是:有个导出任务在一个事务里读了大量数据,干了半小时不 COMMIT,等它结束之后,系统还要花很长时间才能追赶上清理进度。

Undo 相关的调优,思路不是把参数调大掩盖问题,而是先找到那个长期不提交的事务,改掉业务逻辑。Purge 线程的参数比如innodb_purge_threads,默认 4,在大部分场景也够用,但如果你确认 purge 跟不上,先确认事务问题再考虑加线程。

3.4 Binlog 与两阶段提交:redo 和 binlog 谁也不能先"甩锅"

说到 MySQL 日志,很多人一上来就把 redo log 和 binlog 搞混。redo log 是 InnoDB 存储引擎层的日志,物理的,循环写的,主要服务崩溃恢复;binlog 是 MySQL 服务层的日志,逻辑的(记录 SQL 语句或者行变更),追加写的,主要服务主从复制和时间点恢复。两者体系不同,但必须保持一致,不然主从就乱了。

怎么保证一致?答案是两阶段提交。流程是这样:事务修改数据后,先把状态标记为 prepare,写 redo log;接着写 binlog,把 binlog 真正刷到磁盘;最后再更新 redo log 的状态为 commit。崩溃恢复时,如果发现一个事务的 redo log 处于 prepare 状态,就回去查 binlog:如果 binlog 里已经有一条完整的记录,说明这个事务是"准备好提交"的,那就补齐 commit;如果 binlog 里没有或者不完整,说明事务还没提交成功,就回滚。

这套机制保证了 redo log 和 binlog 在任何崩溃点都不会出现"我带了数据但你没记"的情况。所以sync_binlog=1和innodb_flush_log_at_trx_commit=1往往成对出现,两个都等于 1,才是真正的主从一致加崩溃不丢的安全组合。代价是每次提交要刷两个地方,I/O 压力翻倍,这也是很多高并发系统性能瓶颈的来源之一。能不能降?可以,但你得先搞清楚自己的业务能不能接受秒级丢失,而不是盲目地把sync_binlog改成 0 来换吞吐。

4. 崩溃恢复、DoubleWrite 与后台线程:安全兜底机制怎么协作

4.1 崩溃恢复到底在做什么:三阶段回放

数据库宕机后重启,InnoDB 的恢复过程不是"把磁盘上所有数据检查一遍",那样太慢了。它只做三件事:

第一,根据最新的 checkpoint 位点确定起点。checkpoint 之前的日志对应的脏页已经全部落盘,不需要处理,恢复工作只需要从 checkpoint 之后的日志开始。这就是为什么 checkpoint 越频繁,恢复时间越短,但平时的刷盘开销越大,这两者本身就是矛盾,得按业务写入量去平衡。

第二,前滚。从起点开始重放 redo log,把崩溃时内存里还没写回磁盘的页修改重新应用一遍。注意这一步是幂等的,同样的日志应用两次不会出错,所以日志覆盖了一部分已经落盘的页也没关系。

第三,回滚。对于崩溃时尚未提交的事务,InnoDB 通过 undo log 把它们回滚掉,保证重启之后没有"半成品"事务出现在数据里。

整个流程的核心时间取决于 checkpoint 到日志末尾的距离,也就是有效日志量。所以 redo log 文件设多大,除了影响平时写入是否阻塞,还直接影响了崩溃恢复要花多久。文件太大,日志能覆盖很长的写入窗口,恢复时间可能从几秒拖到几分钟。这也是为什么调参要结合恢复时间一起看,不能只图写入顺。

4.2 DoubleWrite:防止"半页写"把数据写坏

日志机制再完善,也还差一个细节问题:页写入磁盘的时候可能写一半断电。比如 16KB 的页,磁盘只写了前 8KB 就掉了电,这个页既不是旧版本,也不是新版本,而是新旧混合的"半页"。redo log 里记录的修改是建立在"我知道这个页原来长什么样"的前提上的,如果原始页已经损坏,redo log 也没法修复它。

DoubleWrite 就是干这个用的。InnoDB 在每次刷脏页之前,会先把这批页完整地写入系统表空间里的 doublewrite buffer 区域,再写入实际的位置。如果在写实际位置时发生半页写,恢复时可以从 doublewrite buffer 里找到一份完整的页副本,重新写入,再继续应用 redo log。

这个机制让每次刷脏多写了一份数据,确实增加写放大。有些 DBA 会在高性能固态硬盘环境下关闭 doublewrite,理由是 SSD 的原子写能力较强,半页写概率极低。我的观点是:默认开启,不要轻易关。即便概率低,一旦碰到页损坏,恢复的难度和风险远高于平时省下的那点开销。只有在你明确了解存储设备特性和业务可容忍性之后,才考虑权衡。

4.3 后台线程与刷脏控制:多大内存撑多大并发

InnoDB 里有一堆后台线程在持续干活,你可能感觉不到它们的存在,但性能瓶颈经常藏在它们之间。最重要的几个:

  • Page Cleaner 线程:负责把脏页刷回磁盘,刷得太快浪费 I/O,刷得太慢 Buffer Pool 会被脏页占满,最终阻塞前台写入。
  • Purge 线程:负责清理无用的 undo log 和被删除标记的记录。
  • Change Buffer 线程:处理索引变更的合并。
  • Checkpoint 线程:推进 checkpoint 位点。

脏页比例由innodb_max_dirty_pages_pct控制,默认已经比较合理,通常不需要调太低。如果设得太低,后台会拼命刷脏,磁盘 I/O 飙升但吞吐量反而下降;设得太高,脏页堆积过多,遇到突然大量写入时会引发刷脏风暴,造成响应时间抖动。我一般保持默认或微调,关键是配合监控观察dirty pages的占比曲线,别凭感觉改。

innodb_io_capacity告诉 InnoDB 你的磁盘能承受多少 I/O,默认 200,这是机械硬盘时代的保守值。用 SSD 的话建议调高,比如 1000 到 2000,否则后台刷脏线程不敢放开干活,脏页越积越多。高阶玩法是启用innodb_io_capacity_max,给它一个更高的峰值限制,让 InnoDB 在压力瞬间可以临时冲一冲。

还有一个容易忽略的 Change Buffer,它专门缓存"二级索引页不在内存时"的变更操作。很多业务大量插入、更新非唯一二级索引,这个机制能显著减少随机读磁盘。它占用 Buffer Pool 的一部分空间,默认是 25%,如果你业务里插入更新很重,可以适当调大一点,但注意它和普通数据页共享内存池,调太大可能挤占热点数据缓存。

5. 排查实战:日志与缓冲池异常怎么定位、怎么止损

5.1 高频异常速查表

调优做久了,你会发现线上问题就那么几类反复出现。我整理了一个速查表,排查的时候按图索骥,效率要高很多:

现象优先排查项常规处理方向
事务提交慢,日志等待严重checkpoint 是否频繁、redo log 是否被写满调大 redo log 容量,检查刷盘 I/O 能力
History list length 持续百万级是否存在未提交的长事务、大事务找到事务源头,修改业务代码,缩短事务时间
Buffer pool hit rate 掉到 0.95 以下是否有全表扫描、Buffer Pool 是否过小优化 SQL、调大 Buffer Pool、打开冷热分区保护
脏页比例长期 90% 以上刷脏线程是否被 I/O 限制、写入压力是否过大调高innodb_io_capacity,检查写入模型是否合理
主从延迟明显sync_binlog与刷盘策略是否不匹配、大事务配合半同步或调整并行复制策略,压缩事务体积
常规查询突然变慢执行计划变化、统计信息过期、缓存被污染重建统计信息、优化索引、分析是否刚有大查询扫过全表

这个表没有写死具体数值,因为不同业务、不同磁盘能力下,合理的窗口差异很大。重要的是养成习惯:每次看到异常指标,先顺着链路往前推——是内存没扛住,还是日志写不赢,还是事务生命周期太长,而不是上来就调参数。

5.2 现场排查常用的几条命令和指标准确解读

再分享几条我实战里必跑的排查命令,都是自带工具,不需要额外安装监控组件。

SHOW ENGINE INNODB STATUS\G

这一条是最基础也最值钱的。重点看四段:

  • LOG段的Log sequence number、Log flushed up to和Last checkpoint at。三者距离过大,说明刷盘压力大或者 checkpoint 滞后。
  • TRANSACTIONS段的History list length,判断 undo 清理是否正常。
  • BUFFER POOL AND MEMORY段的Buffer pool hit rate,以及Free buffers剩余量。
  • INSERT BUFFER AND ADAPTIVE HASH INDEX段的变化,判断 Change Buffer 是否在忙碌。
SELECT * FROM performance_schema.events_statements_summary_by_digest ORDER BY SUM_TIMER_WAIT DESC LIMIT 10;

这条用来快速定位哪些 SQL 模板消耗了最多执行时间,配合慢日志一起看,能快速锁定"高耗时高频率"的语句。

SELECT ENGINE, TABLE_NAME, INDEX_NAME, STAT_NAME, STAT_VALUE FROM information_schema.innodb_metrics WHERE STAT_NAME IN ('dml_inserts', 'dml_updates', 'dml_deletes');

如果想知道写量到底有多大、有没有突变,可以看 InnoDB 内部的 DML 计数器。很多写入问题不是参数引入的,而是业务流量涨上去了,InnoDB 机制来不及吸收,这个指标能帮你和业务方对齐"是不是数据量涨了"这个事实。

还有一句很常用的:

SHOW GLOBAL STATUS LIKE 'Innodb_%';

里面Innodb_row_lock_waits、Innodb_buffer_pool_wait_free这类计数器,能直接验证你怀疑的方向。比如buffer_pool_wait_free一直有计数,说明后台腾不出空闲页,前台请求一直在等,这时候加内存或者优化扫描才是正解。

5.3 一次典型调优过程复盘

最后复盘一个典型的场景。某业务高峰期延迟飙升,应用日志里全是数据库超时,监控面板显示磁盘 I/O 使用率接近饱和。第一轮处理,有人直接把innodb_buffer_pool_size从 16GB 加到 32GB,结果磁盘 I/O 还是爆满,问题没解决。

重新排查,SHOW ENGINE INNODB STATUS发现Log sequence number和Last checkpoint at之间差距巨大,redo log 写了没多久就触发 checkpoint,而且提交线程大量阻塞在日志等待。这显然不是缓存不够,而是写入量超过了磁盘刷盘能力。

进一步确认:这台机器用的是机械存储阵列,sync_binlog=1加innodb_flush_log_at_trx_commit=1的双 1 配置,每个事务提交都伴随两次 fsync,I/O 队列直接被打满。处理和预期不同,不是加大 redo 容量(当然也加了),而是把存储层从机械盘切到 SSD 固态存储,再把innodb_io_capacity适当放开。切换之后,同样的 QPS 下 I/O 使用率掉到了 30% 以内,延迟曲线马上平了。

这个案例说明一个道理:调优是跟着机制找瓶颈,不是对着监控猜参数。第一次改 Buffer Pool 为什么没用?因为当时的瓶颈根本不在缓存命中率,而在日志刷盘的 I/O 能力。只有把存储结构、日志机制、内存缓存这三条线在脑子里串起来,才能少走这个弯路。

我自己这些年做 MySQL 调优,最深的体会就是:先别急着抄网上的参数模板。你把 InnoDB 的存储模型、Buffer Pool 的 LRU 机制、redo log 的环形空间、undo log 的版本链、binlog 的两阶段提交这五件事连起来串一遍,绝大多数性能问题自己就能推到根源上。真上了生产,第一眼看History list length,第二眼看Buffer pool hit rate,第三眼看 redo log 的 checkpoint 跨度,再结合业务侧有没有大事务、全表扫描,问题基本就浮出水面了。这一章把 InnoDB 的地基讲透了,下一章我们再把这些原理落到实际的索引优化和 SQL 改写上,到时候你会发现很多工具和方法,用起来心里会特别有底。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进