MySQL的InnoDB存储引擎:从Page到Extent的物理存储结构与空间管理

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的行记录格式包括COMPACTREDUNDANTDYNAMICCOMPRESSED

一个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,方便快速定位

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注