MySQL的`Innodb“Page`:其物理存储结构与行格式(`Row Format`)

Innodb Page 的物理存储结构与行格式

大家好,今天我们来深入探讨 InnoDB 存储引擎中最为核心的概念之一:Page。Page 是 InnoDB 存储引擎管理数据的最小单元,理解 Page 的物理结构和行格式对于优化数据库性能至关重要。

一、Page 的物理存储结构

InnoDB 使用固定大小的 Page 来存储数据,默认大小为 16KB。可以通过参数 innodb_page_size 修改,但通常不建议修改,因为修改后会对性能产生影响。

一个 Page 主要由以下几个部分组成:

组成部分 大小 (Bytes) 描述
File Header 38 记录 Page 的一些通用的信息,例如 Page 的类型、Page 的校验和、所属的表空间 ID 等。
Page Header 56 记录 Page 自身的一些信息,例如 Page 中记录的数量、Page 中第一个记录的地址、Page 中最后一个记录的地址、Page 中空闲空间的起始地址等。
User Records 可变 实际存储行记录的部分。不同的行格式 (Row Format) 会影响这部分的存储方式。
Free Space 可变 Page 中未被使用的空间。新记录会首先分配到 Free Space 中。
System Records 可变 系统记录,例如 Infimum 记录和 Supremum 记录。这两个记录是 InnoDB 自动创建的,分别表示 Page 中最小和最大的记录。它们不存储实际的用户数据,主要用于简化查询操作。
File Trailer 8 用于校验 Page 的完整性。包含 Page 的校验和以及 Page 的 LSN (Log Sequence Number) 的低 4 个字节。

1. File Header

File Header 包含了 Page 的通用信息,其结构如下:

名称 大小 (Bytes) 描述
FIL_PAGE_SPACE_OR_CHKSUM 4 Page 所在的表空间 ID。如果设置了 innodb_checksums,则这 4 个字节用于存储 Page 的校验和。
FIL_PAGE_OFFSET 4 Page 在表空间中的偏移量,也就是 Page 的编号。
FIL_PAGE_PREV 4 上一个 Page 的偏移量。如果 Page 是链表中的第一个 Page,则该值为 NULL。
FIL_PAGE_NEXT 4 下一个 Page 的偏移量。如果 Page 是链表中的最后一个 Page,则该值为 NULL。
FIL_PAGE_LSN 8 Page 最后被修改的日志序列号 (LSN)。LSN 用于恢复操作,确保数据的一致性。
FIL_PAGE_TYPE 2 Page 的类型。常见的类型包括:INDEX (索引页)、UNDO LOG PAGE (Undo 日志页)、SYSTEM PAGE (系统页) 等。
FIL_PAGE_FILE_FLUSH_LSN 8 Page 被刷新到磁盘时的 LSN。
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID 4 用于归档的日志编号或表空间 ID。

2. Page Header

Page Header 记录了 Page 自身的状态信息,其结构如下:

名称 大小 (Bytes) 描述
PAGE_N_DIR_SLOTS 2 Page 中目录槽 (Directory Slot) 的数量。目录槽用于快速定位记录,类似于哈希表的桶。
PAGE_HEAP_TOP 2 Page 中堆的顶部地址,也就是 Free Space 的起始地址。
PAGE_N_HEAP 2 Page 中堆的数量。
PAGE_FREE 2 指向 Free Space 链表中第一个 Page 的指针。
PAGE_GARBAGE 2 已删除记录占用的字节数。
PAGE_LAST_INSERT 2 最后一个插入的记录的地址。
PAGE_DIRECTION 2 记录插入的方向。用于优化插入操作。
PAGE_N_DIRECTION 2 连续往同一个方向插入的记录的数量。
PAGE_N_RECS 2 Page 中记录的数量(包括 Infimum 和 Supremum 记录)。
PAGE_MAX_TRX_ID 8 修改当前页的最大事务ID
PAGE_LSN 8 当前页的LSN
PAGE_SPACE_ID 4 当前页的表空间ID
PAGE_CHECKSUM 4 当前页的校验和

3. User Records

User Records 存储了实际的行记录。具体的存储格式取决于所使用的行格式 (Row Format)。我们将在下一节详细讨论行格式。

4. Free Space

Free Space 是 Page 中未被使用的空间。当需要插入新的记录时,InnoDB 会首先从 Free Space 中分配空间。

5. System Records (Infimum 和 Supremum Records)

Infimum 和 Supremum 记录是 InnoDB 自动创建的两个虚拟记录,分别表示 Page 中最小和最大的记录。它们不存储实际的用户数据,主要用于简化查询操作。

6. File Trailer

File Trailer 用于校验 Page 的完整性。包含 Page 的校验和以及 Page 的 LSN (Log Sequence Number) 的低 4 个字节。校验和用于检测数据是否损坏,LSN 用于恢复操作。

二、行格式 (Row Format)

InnoDB 提供了多种行格式,不同的行格式会影响 User Records 的存储方式。常见的行格式包括:

  • REDUNDANT (已废弃)
  • COMPACT
  • DYNAMIC
  • COMPRESSED

MySQL 5.7 及之前的版本默认使用 COMPACT 行格式,MySQL 8.0 默认使用 DYNAMIC 行格式。

我们可以通过以下 SQL 语句查看表的行格式:

SHOW TABLE STATUS LIKE 'your_table_name'G

其中 Row_format 字段显示了表的行格式。

接下来,我们分别介绍 COMPACTDYNAMIC 行格式。

1. COMPACT 行格式

COMPACT 行格式的设计目标是尽可能地减少存储空间。其记录结构如下:

部分 描述
记录头信息 (Record Header) 5 字节。记录头信息包含了记录的一些状态信息,例如:记录是否被删除、记录的类型、指向下一条记录的指针等。
列长度信息 (Variable Length Column Length) 如果记录中包含变长列(例如 VARCHAR、TEXT、BLOB 等),则这部分存储了变长列的长度。
NULL值标志位 (NULL Flags) 如果记录中包含允许为 NULL 的列,则这部分存储了 NULL 值标志位。每个允许为 NULL 的列对应一个标志位,如果该列的值为 NULL,则对应的标志位被设置为 1。
实际列数据 (Actual Column Data) 实际存储列数据的部分。

记录头信息 (Record Header)

记录头信息包含了记录的一些状态信息,其结构如下:

名称 大小 (Bits) 描述
delete_mask 1 标记记录是否被删除。
min_rec_mask 1 标记记录是否为 B+ 树的最小记录。
n_owned 4 表示当前记录拥有的记录数。仅在目录槽 (Directory Slot) 对应的记录中有效。
heap_no 13 表示当前记录在堆中的编号。
record_type 3 表示记录的类型。0 表示普通记录,1 表示 B+ 树非叶子节点记录,2 表示 Infimum 记录,3 表示 Supremum 记录。
next_record 16 指向下一条记录的指针。通过 next_record 可以将 Page 中的记录连接成一个单向链表。

列长度信息 (Variable Length Column Length)

如果记录中包含变长列(例如 VARCHAR、TEXT、BLOB 等),则这部分存储了变长列的长度。

  • 如果变长列的实际长度小于 255 字节,则用 1 个字节存储长度。
  • 如果变长列的实际长度大于 255 字节,则用 2 个字节存储长度。

NULL值标志位 (NULL Flags)

如果记录中包含允许为 NULL 的列,则这部分存储了 NULL 值标志位。每个允许为 NULL 的列对应一个标志位,如果该列的值为 NULL,则对应的标志位被设置为 1。

实际列数据 (Actual Column Data)

实际存储列数据的部分。

2. DYNAMIC 行格式

DYNAMIC 行格式是对 COMPACT 行格式的改进。它主要解决了 COMPACT 行格式在存储大 TEXT 或 BLOB 列时效率较低的问题。

DYNAMIC 行格式中,如果 TEXT 或 BLOB 列的长度超过一定阈值(默认为 40 字节),则只会将列的一部分内容(通常是 20 字节)存储在 User Records 中,而将剩余的内容存储在溢出页 (Overflow Page) 中。User Records 中存储的部分内容包含了指向溢出页的指针。

DYNAMIC 行格式的记录结构与 COMPACT 行格式类似,主要的区别在于对 TEXT 和 BLOB 列的处理方式。

3. 行溢出(Row Overflow)

当一行数据过大,单个Page无法容纳时,就会发生行溢出。DYNAMICCOMPRESSED行格式就是为了更好地处理行溢出而设计的。

三、Page 目录

为了加快在 Page 中查找记录的速度,InnoDB 使用了 Page 目录 (Page Directory)。Page 目录类似于哈希表的桶,它将 Page 中的记录分成若干个组,每个组对应一个目录槽 (Directory Slot)。目录槽存储了组中最后一条记录的地址。

当我们需要在 Page 中查找一条记录时,首先通过二分查找找到对应的目录槽,然后从目录槽对应的记录开始,沿着链表遍历,直到找到目标记录。

四、代码示例

虽然无法直接访问 InnoDB 的物理存储结构,但可以通过一些工具来分析 Page 的内容。例如,可以使用 hexdump 命令查看 Page 的十六进制表示,然后根据 Page 的结构来解析数据。

以下是一个使用 Python 解析 COMPACT 行格式的示例代码:

import struct

def parse_compact_row(row_data):
    """
    解析 COMPACT 行格式的数据。
    """

    # 1. 解析记录头信息 (Record Header)
    header = struct.unpack('<BBBBBB', row_data[:6]) # 这里假设只取前6个字节,实际是5个字节,第六个字节可能是其他信息
    delete_mask = header[0] & 0x01
    min_rec_mask = header[0] & 0x02
    n_owned = (header[0] >> 2) & 0x0F
    heap_no = (header[1] << 8 | header[2]) & 0x1FFF
    record_type = (header[3] >> 13) & 0x07
    next_record = header[4] << 8 | header[5]

    # 2. 解析列长度信息 (Variable Length Column Length) 和 NULL 值标志位 (NULL Flags)
    # 这部分的代码需要根据表的结构来动态解析
    # 这里只是一个示例,假设表包含一个 VARCHAR 列和一个 INT 列,VARCHAR 列允许为 NULL

    offset = 6  # 记录头信息的长度

    # 解析 VARCHAR 列的长度
    varchar_length = struct.unpack('<H', row_data[offset:offset+2])[0]
    offset += 2

    # 解析 NULL 值标志位
    null_flags = struct.unpack('<B', row_data[offset:offset+1])[0]
    offset += 1

    # 3. 解析实际列数据 (Actual Column Data)
    varchar_data = row_data[offset:offset+varchar_length].decode('utf-8')
    offset += varchar_length

    int_data = struct.unpack('<i', row_data[offset:offset+4])[0]
    offset += 4

    print(f"Delete Mask: {delete_mask}")
    print(f"Min Rec Mask: {min_rec_mask}")
    print(f"N Owned: {n_owned}")
    print(f"Heap No: {heap_no}")
    print(f"Record Type: {record_type}")
    print(f"Next Record: {next_record}")
    print(f"VARCHAR Data: {varchar_data}")
    print(f"INT Data: {int_data}")

# 示例数据 (需要替换为实际的 COMPACT 行格式数据)
row_data = b'x00x00x00x00x00x00x05x00x00x05hellox01x00x00x00'

parse_compact_row(row_data)

注意:

  • 以上代码只是一个示例,实际的解析过程会更加复杂,需要根据表的结构来动态解析。
  • 获取实际的 COMPACT 行格式数据需要使用一些专业的工具,例如 hexdump
  • 在生产环境中,不建议直接解析 InnoDB 的物理存储结构,因为这可能会导致数据损坏。

五、总结,InnoDB Page 的重要性

InnoDB Page 作为 InnoDB 存储引擎中管理数据的最小单元,其物理结构和行格式对于数据库的性能至关重要。理解 Page 的结构可以帮助我们更好地优化数据库设计和查询。行格式直接影响存储效率,选择合适的行格式可以减少磁盘空间占用,提高 IO 效率。

发表回复

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