MySQL InnoDB 存储引擎:从Page到Extent的物理存储结构与空间管理
大家好,今天我们来深入探讨MySQL InnoDB存储引擎的物理存储结构和空间管理机制。理解这些底层细节,对于我们优化数据库性能,进行故障排除以及更好地利用InnoDB的特性至关重要。
1. InnoDB的物理存储结构概览
InnoDB的物理存储结构可以概括为以下几个层次:
- Page (页): 这是InnoDB磁盘管理的最小单元,也是数据读写的最小单元。默认大小为16KB。
- Extent (区): 由连续的Page组成,默认包含64个Page,也就是1MB。
- Segment (段): 由多个Extent组成,用于存储特定类型的数据,例如索引数据、表数据等。
- Tablespace (表空间): 最大的逻辑存储单元,可以包含多个Segment。InnoDB有系统表空间(system tablespace)和独立表空间(file-per-table tablespace)两种类型。
我们将会重点关注Page和Extent,了解它们是如何组织数据以及InnoDB如何管理它们。
2. Page (页) 的结构
InnoDB Page是数据存储和管理的核心。每个Page都包含以下几个部分:
| 区域 | 大小 (字节) | 描述
| 区域 | 大小 (字节) | 描述
| 头部 (Header) | 38 字节 | 包含页的类型、校验和、以及其他控制信息。 , and you might have to re-read something several times.
We’ll use Python-like pseudo code for illustrations. Remember, this is simplified and doesn’t directly translate to InnoDB’s C++ implementation.
2.1 Page Header 和 Page Trailer
-
Page Header: 包含Page的元数据,如Page类型(数据页、索引页、undo页等)、LSN(Log Sequence Number,用于崩溃恢复)、Page在B+树中的位置信息等。
-
Page Trailer: 包含Page的校验和,用于检测数据是否损坏。
2.2 User Records (用户记录)
这是存储实际数据的地方。对于数据页,User Records存储表中的行数据。数据按照一定的格式组织,通常是行记录格式,例如InnoDB的行记录格式包括COMPACT、REDUNDANT、DYNAMIC和COMPRESSED。
一个Page通常可以存储多条记录。记录之间通过链表的方式组织,方便遍历。
2.3 Free Space (空闲空间)
用于存储新插入的记录。当Page中的空闲空间不足时,InnoDB会尝试进行Page拆分或重用其他Page。
2.4 Page Directory (页目录)
用于快速定位Page中的记录。Page Directory将Page中的记录分组,每组记录对应一个槽(slot)。通过二分查找Page Directory,可以快速找到包含目标记录的槽,然后遍历槽中的记录即可。
2.5 Infimum and Supremum Records (最小记录和最大记录)
这两个是虚拟的记录,分别位于Page的开头和结尾,用于简化Page内记录的查找。Infimum记录小于Page中的所有记录,Supremum记录大于Page中的所有记录。
3. Extent (区) 的概念
Extent是由连续的Page组成的一组物理存储单元。默认情况下,一个Extent包含64个Page,也就是1MB。
3.1 Extent的作用
- 空间分配: InnoDB的空间分配以Extent为单位。当需要分配空间时,InnoDB会分配一个或多个Extent。
- 减少碎片: 使用Extent可以减少磁盘碎片。因为连续的Page存储在一起,可以减少随机I/O。
- 顺序I/O: Extent有利于顺序I/O。当读取大量数据时,InnoDB可以顺序读取Extent中的Page,提高读取效率。
3.2 Extent的类型
- 混合Extent (Mixed Extent): 一个Extent可以包含来自不同Segment的Page。InnoDB最初分配Extent时,通常会分配混合Extent。
- 独占Extent (Dedicated Extent): 一个Extent只包含来自同一个Segment的Page。当混合Extent中的Page全部被同一个Segment占用时,该Extent就会变成独占Extent。
4. InnoDB的空间管理
InnoDB的空间管理主要涉及以下几个方面:
- 空间分配: 如何分配新的Extent和Page。
- 空间回收: 如何回收不再使用的Extent和Page。
- 碎片整理: 如何减少磁盘碎片。
4.1 空间分配
InnoDB使用B+树来管理空闲空间。B+树的叶子节点存储空闲Extent的信息。当需要分配空间时,InnoDB会查找B+树,找到合适的空闲Extent,然后将其分配给相应的Segment。
InnoDB维护了几种B+树来管理不同类型的空闲空间:
- FSP_HDR: 用于管理整个表空间的文件头信息。
- XDES: 用于管理Extent的元数据信息。
- IBUF_BITMAP: 用于管理Insert Buffer的位图信息。
4.2 空间回收
当Segment不再需要某个Extent时,InnoDB会将该Extent标记为空闲,并将其信息添加到空闲空间B+树中。
4.3 碎片整理
磁盘碎片会降低数据库的性能。InnoDB提供了一些机制来减少磁盘碎片,例如:
- OPTIMIZE TABLE: 可以重建表,从而整理表中的数据和索引,减少碎片。
- Online DDL: 在MySQL 5.6及更高版本中,InnoDB支持在线DDL操作,可以在不阻塞DML操作的情况下修改表结构,减少碎片。
5. 代码示例 (伪代码)
为了更好地理解InnoDB的空间管理,我们可以使用伪代码来模拟一些关键操作。
5.1 分配Extent
class ExtentManager:
def __init__(self, free_space_btree):
self.free_space_btree = free_space_btree
def allocate_extent(self, segment_id, size_in_pages):
"""
从空闲空间B+树中分配一个Extent。
"""
extent = self.free_space_btree.find_suitable_extent(size_in_pages)
if extent:
self.free_space_btree.remove_extent(extent)
extent.segment_id = segment_id
return extent
else:
return None # 没有足够的空闲空间
5.2 回收Extent
class ExtentManager:
def __init__(self, free_space_btree):
self.free_space_btree = free_space_btree
def deallocate_extent(self, extent):
"""
将一个Extent标记为空闲,并将其添加到空闲空间B+树中。
"""
extent.segment_id = None
self.free_space_btree.insert_extent(extent)
5.3 Page的插入操作
class Page:
def __init__(self, page_id, page_type):
self.page_id = page_id
self.page_type = page_type
self.records = []
self.free_space = 16384 # 16KB
self.page_directory = []
def insert_record(self, record):
"""
将一条记录插入到Page中。
"""
record_size = len(record.data)
if self.free_space >= record_size:
self.records.append(record)
self.free_space -= record_size
self.update_page_directory(record)
return True
else:
return False # Page空间不足
def update_page_directory(self, record):
"""
更新Page Directory,方便快速定位