InnoDB的“身体结构”:页、Buffer Pool与Redo Log的底层奥秘
欢迎来到MySQL InnoDB存储引擎的“解剖室”很多人每天都在写SQL却从未见过数据在磁盘上真正的模样。当面试官问“为什么InnoDB比MyISAM快”或者“数据库宕机了数据是怎么恢复的”如果你只能回答“因为有日志”或者“因为有缓存”那还停留在“知其然”的层面。今天我们要深入到字节码和磁盘扇区的级别彻底拆解InnoDB的“身体结构”看看它是如何通过精妙的设计在速度与可靠性之间走钢丝的。物理存储结构数据在磁盘上的“俄罗斯套娃”首先我们要打破一个迷思数据库并不是把数据像流水一样随意倒在磁盘上的。InnoDB为了高效管理设计了一套严密的层级结构就像一个巨大的图书馆。InnoDB的存储结构从大到小依次是表空间Tablespace、段Segment、区Extent、页Page、行Row。表空间这是最大的逻辑容器你可以把它想象成整个图书馆大楼。段这是不同数据的分类区域比如“数据段”存放表数据“索引段”存放索引数据“回滚段”存放Undo Log。区这是磁盘上连续的一块空间大小固定为1MB。InnoDB在向操作系统申请空间时是以“区”为单位申请的。页这是InnoDB磁盘与内存交互的最小单位默认大小为16KB。这里有一个关键的架构师洞察为什么是16KB如果页太小比如4KB那么读取一行数据虽然快但页目录会很大管理成本高如果页太大比如64KB读取一行数据需要加载大量无关数据到内存造成浪费。16KB是MySQL社区经过长期权衡得出的“黄金尺寸”。16KB一个页不仅仅是行记录的集合它由页头Page Header简介、用户记录User Records数据、空闲空间Free Space空闲、页目录Page Directory二分查询和页尾Page Footer总结\下一个组成。------------------------------------------------------- | Page Header (页头) | -- 存放元数据页的类型、LSN、下一条记录指针等 | (36~56 Bytes) | ------------------------------------------------------- | User Records (用户记录) | -- 真正的行数据从上往下生长 | (Row 1, Row 2, ...) | ------------------------------------------------------- | Free Space (空闲空间) | -- 还没被使用的空间 ------------------------------------------------------- | Page Directory (页目录) | -- 稀疏索引指向记录用于二分查找 | (Slot 1, Slot 2, ...) | ------------------------------------------------------- | Page Footer (页尾) | -- 存放页内记录数量、下一个页ID等 | (8 Bytes) | -------------------------------------------------------关键代码视角行记录的物理存储C 语言层面行记录并不是像数组一样连续存储的而是通过Next Record Offset指针串联起来的// 源码简化版行记录的物理结构 struct rec_t { // 变长字段长度列表 // 空值列表 // 事务 ID (6字节) // 回滚指针 (7字节) // 实际列数据... // 记录头信息 (5字节) // 其中包含 next_record: 指向下一条记录的偏移量 (相对于当前记录) };为什么页目录Page Directory很重要因为行记录是链表串起来的如果要查找某条记录从头遍历是 O(N)O(N) 。页目录将记录分组每组 8~16 条目录项指向每组的最大值。查找时先对页目录二分查找 O(logN)O(logN) 再在组内线性查找。这就是为什么 B 树索引在页内查找也很快的原因。行这是最小的数据单元。当你执行一条SELECT * FROM user WHERE id 1时InnoDB并不是只把id1的那一行数据从磁盘读到内存而是把这一行所在的整个页16KB全部加载进来。这就是“局部性原理”在数据库中的应用。内存结构Buffer Pool与“书桌”理论既然磁盘读写这么慢机械硬盘的随机I/O大约只有几百次/秒而内存是纳秒级InnoDB是怎么做到每秒处理上万次查询的秘密就在于Buffer Pool。你可以把Buffer Pool想象成你的书桌而磁盘是巨大的书架。如果你想看书查询数据你不会每次都跑去书架拿而是先把书拿到书桌上。如果你要写书修改数据你也不会直接在书架上涂改而是在书桌上改好等有空了再放回去。内存布局控制块与数据页在源码中Buffer Pool 被划分为两个主要部分控制块数组Control Blocks存放buf_block_t结构体包含页的元数据如 LRU 状态、脏页标志、哈希指针。数据页数组Data Blocks存放真正的 16KB 数据。对应关系Control Block[i]管理Data Block[i]。链表管理不仅仅是 LRUInnoDB 维护了三条核心链表来管理这些块Free List空闲块链表。Flush List脏页链表。注意这里只存脏页按 LSN 排序用于刷盘。LRU List存放所有已使用的页。源码视角LRU 链表的优化Midpoint Insertion标准的 LRU 有个缺陷全表扫描会把热点数据挤出去。InnoDB 将 LRU 链表分为Young 区前 5/8和Old 区后 3/8。新页插入默认插入到Old 区的头部。访问页如果页在Old 区不立即移动到头部而是更新“最后一次访问时间”。只有当时间间隔超过innodb_old_blocks_time默认 1000ms再次访问才移动到Young 区头部。如果页在Young 区直接移动到头部。// 模拟 InnoDB LRU 优化逻辑 public void accessPage(Page page) { if (page.isInOldSublist()) { if (System.currentTimeMillis() - page.lastAccessed 1000) { // 只有间隔超过 1s 再次访问才晋升到 Young 区 lruList.moveToYoungHead(page); } page.lastAccessed System.currentTimeMillis(); } else { // 在 Young 区直接移到头部 lruList.moveToHead(page); } }Buffer Pool的读流程当你查询数据时InnoDB首先会检查Buffer Pool中是否已经有了这个页。如果存在命中直接从内存返回数据速度极快。如果不存在未命中则从磁盘读取该页到Buffer Pool然后再返回数据。Buffer Pool的写流程与WAL当你更新数据时InnoDB不会立即把数据刷到磁盘因为随机写磁盘太慢了。它只是在Buffer Pool中修改内存页并将这个页标记为“脏页”。然后后台线程会在合适的时候比如Buffer Pool满了、或者系统空闲时异步地把脏页刷新到磁盘。这就引出了一个核心问题如果在你修改了内存但还没来得及刷盘的时候数据库宕机了数据岂不是丢了这就必须请出InnoDB的“救命稻草”——Redo Log。日志系统Redo Log与“草稿纸”机制Redo Log是InnoDB实现事务持久性的关键。它的原理可以用“草稿纸”来比喻。假设你要修改书上的内容但怕改错了或者还没改完灯就灭了。于是你拿出一本“草稿纸”Redo Log。在修改书之前你先把要修改的内容比如“把第10页的‘张三’改成‘李四’”写在草稿纸上。只要草稿纸写下来了哪怕书还没改灯灭了你重启后也能根据草稿纸把书改对。这就是WAL技术预写日志。先写Redo Log顺序写速度极快再写Buffer Pool内存操作速度极快最后异步刷盘随机写慢。Redo Log的物理结构Redo Log记录的是物理日志它记录的是“在某个数据页上做了什么修改”。它的大小是固定的通常配置为两个文件ib_logfile0, ib_logfile1循环写入。当写满时会覆盖旧的日志。覆盖的前提是旧的日志对应的数据页已经成功刷到了磁盘。这里涉及到一个关键指针Checkpoint。Checkpoint指向的是“已经刷盘的日志位置”。Write Pos指向的是“当前写入的位置”。当Write Pos追上Checkpoint时说明日志满了必须停止写入强制触发刷盘Checkpoint推进否则数据库会阻塞。Redo Log与Binlog的区别很多开发者容易混淆这两个日志。Redo Log是InnoDB引擎特有的是物理日志记录的是数据页的物理修改用于崩溃恢复。Binlog是MySQL Server层实现的是逻辑日志记录的是SQL语句或者行变更事件用于主从复制和数据归档。两者通过两阶段提交来保证数据一致性。崩溃恢复LSN与“时间机器” 大智慧啊InnoDB是如何做到崩溃恢复的靠的是LSN日志序列号。LSN是一个单调递增的64 位的整数不仅是日志的序号更是字节偏移量Log LSN当前 Redo Log 写到了第几个字节。Page LSN数据页最后一次被修改时的 LSN。Checkpoint LSN数据已经刷盘到的位置。每一条Redo Log都有一个LSN每一个数据页的头部也记录了它最后一次被修改时的LSN。写入流程的底层代码逻辑在源码log_write_up_to函数中写入 Redo Log 涉及复杂的锁和内存拷贝。// 伪代码Redo Log 写入流程 void log_write_up_to(lsn_t lsn) { // 1. 获取 log_write_lock mutex_enter(log_sys-write_lock); // 2. 计算写入位置 // 利用取模运算实现环形缓冲区 ulint offset lsn % log_sys-log_group_capacity; // 3. 将日志从 Log Buffer 拷贝到 Redo Log 文件 // 这是顺序写极快 os_file_write(log_file, log_buffer, offset, size); // 4. 更新 write_lsn log_sys-write_lsn lsn; mutex_exit(log_sys-write_lock); }Checkpoint 机制刷盘的“水位线”Checkpoint 是 InnoDB 崩溃恢复的核心。Fuzzy CheckpointInnoDB 不会一次性把所有脏页刷盘那样会卡死而是异步、分批地刷。Checkpoint LSN指向“所有 LSN 小于它的脏页都已经刷盘”的位置。当数据库重启时InnoDB会对比数据页的LSN和Redo Log的LSN。数据页的LSN小于Redo Log的LSN说明这个页在崩溃前被修改了但没刷盘需要用Redo Log重做。如果数据页的LSN大于Redo Log的LSN说明这个页是新的不需要恢复。细化来说读取 Redo Log 头找到 Checkpoint LSN。从 Checkpoint LSN 开始扫描 Redo Log。对于每一条日志读取对应数据页的 Page LSN。如果Page LSN Redo Log LSN说明页是旧的需要重做Redo。如果Page LSN Redo Log LSN说明页已经是新的跳过。进阶优化Change Buffer与写缓冲InnoDB加速写的机制Change Buffer。它主要用于非唯一 二级索引的更新。当你更新一个二级索引时如果这个索引页不在Buffer Pool中InnoDB不会立即从磁盘读取该页而是把更新操作缓存在Change Buffer中。// 模拟 Change Buffer 逻辑 public void updateSecondaryIndex(IndexPage page, Update update) { if (bufferPool.contains(page.getId())) { // 页在内存直接修改 page.apply(update); } else { // 页不在内存写入 Change Buffer // 这是一个磁盘操作但写的是系统表空间通常是顺序或局部顺序 changeBuffer.insert(page.getId(), update); } }等下次这个页被读取到内存时再合并更新。这样可以避免大量的随机磁盘I/O。注意唯一索引不能使用Change Buffer因为更新唯一索引时必须检查唯一性这需要读取磁盘上的旧数据无法缓冲。总结InnoDB的伟大之处在于它深刻地理解了计算机硬件的特性。它用Buffer Pool解决了内存与磁盘的速度差。它用Redo Log解决了随机写与顺序写的速度差。它用页结构解决了数据管理的粒度问题。细化通过上面的拆解我们可以看到 InnoDB 的设计哲学空间换时间用 16KB 的页结构包含 Header/Footer/Directory来管理数据虽然浪费了少量空间但换取了高效的链表管理和二分查找。用巨大的 Buffer Pool 占用内存换取磁盘 I/O 的减少。顺序写换随机写利用 Redo Log 的顺序写Log Buffer - Redo Log File来规避数据页的随机写Data Page - Disk。异步化与批处理脏页通过 Flush List 异步刷盘。Change Buffer 将多次对同一页的修改合并为一次。最后送上金句“MySQL的性能瓶颈通常在磁盘I/O。InnoDB的伟大之处在于用‘内存Buffer Pool’换‘磁盘速度’用‘顺序写Redo Log’换‘随机写’。理解了这一点你就理解了数据库性能优化的本质。”“数据库的底层优化本质上都是在与物理硬件的特性做斗争。InnoDB 通过 LRU 链表对抗内存的有限性通过 WAL 对抗磁盘随机写的低性能通过 B 树对抗数据检索的高延迟。理解了这些底层结构你就不再是在写 SQL而是在指挥硬件跳舞。”
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2459695.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!