Skip to content

Files, Pages, Tuples

这节课主要内容是 Background, File Storage, Page Layout, Tuple Layout

Disk-Based Architecture

首先,数据库默认的主要存储位置位于持久化存储空间中,也就是硬盘,例如 SSD, HDD ,他们都是不能被直接操作的。如果要读取或者写入,需要先将数据加载到内存中,然后在内存中读取和写入。

Sequential VS Random Access

随机读写取在硬盘中,几乎总是比顺序读写中慢,因此在磁盘的读取和写入中,经可能增加顺序读写,减少随机读写。

Disk-Oriented DBMS

有的数据库文件存储为单个文件,例如 SQL-Lite ,但是大多数是多个文件,例如 MySQL ,但是不管怎么样,这些文件对于操作系统来说,都是一样的,本质都是使用 fopen(), fread(), fwrite() 操作他们。

这些文件中又进一步被分为许多长度固定的 Page ,通常在文件头会存储着 Page Directory 。

当我要使用时,内存中通常会有一个 Buffer Pool ,还会有一个执行引擎。
假设需要查询,执行引擎说:“我想要第二个 Page 的内容”,这时候就会将文件头中的 Page Directory 读进 Buffer Pool。
然后从 Page Directory 中可以找到第二个 Page 的位置,加载进内存中。
然后将指向该 Page 的指针交给执行引擎,执行引擎就可以使用第二个 Page 的数据了。
执行引擎执行结束之前 Buffer Pool 会通过一种叫做 Page Pinning 的技术保证指针不会被替换或者与另一个页面交换,从而保证数据访问的绝对安全。
当执行引擎运行结束后,会将数据更新到内存中的第二个 Page ,同时告诉 Buffer Pool :“我完成了”。
接着 Buffer Pool 就会将内存中的第二个 Page 写入硬盘的第二个 Page 中。

File Storage

现在的数据库文件格式,都是每个数据库独有的,不同的数据库文件在不同的数据库不通用。
但是现在有了一种新的趋势,就是可移植的文件格式,例如: Parquet。

Storage Manager

大多数数据库会有一个成为 Storage Manager 的东西,它是系统负责读取和写入磁盘数据的组件。通常是不希望让操作系统来调度和管理数据库文件,这会让数据库性能降低。

Storage Manager 会跟踪数据库文件中 Page 的读取和写入,并且它会跟踪可用空间。

Database Page

Page 是一个固定大小的数据块,它基本包含数据库系统中的所有内容,例如: Tuple, Meta-Data, Indexs, Log Record 。

每个 Page 都有一个唯一的标识符(Page ID) ,Page ID 是数据库系统需要维护的唯一编号,用于跟踪 Page ,也有可能作为一个逻辑位置当作偏移量使用。

Page

在系统中有三种 Page

Hardware Page (通常 4KB)
OS Page (通常 4KB, x64 2MB/1GB)
Database Page (512B-32KB)

Page 的大小没有好和坏的区分,只有合适与不合适的区别,通常来说,读密集型场景 Page 长度大比较好,写密集型场景 Page 长度小比较好。

Heap File

Heap File 是一个无序的 Page 集合,这些 Page 在逻辑上没有顺序,都是随机的,Page 里的 Tuples 也是无序的,当你插入一条数据时,系统找到第一个有空位的地方就会把它放进去,不会考虑任何排序。

由于 Heap File 是无序的,插入新数据时,要找到空闲的位置,不能每次都遍历整个文件来寻找,这样太慢了,因此会使用 Page Directory 来管理。

在单数据库文件中,定位只需要进行简单的数学计算就好

物理地址 = 文件起始地址 + (Page ID * Page Size)

在多数据库文件中,需要先找到 Page 在哪个文件里,这里就需要通过 Page Directory ,它会找到 Page 的位置,然后通过简单的数学计算就可以了

Page Directory

Page Directory 不存储用户数据,指挥存储 Metadata ,也就是关于数据的数据。

Page Directory 的职责

  • 追踪 Data Page 的位置

    它维护一个映射关系,告诉系统某个逻辑对象(如表X、索引Y)的数据页都存储在哪些物理文件里。

  • 追踪 Meta Page 的内容
    • 管理空闲空间(Free Space Map - FSM)

      它记录了每个 Page 还剩下多少可用空间。当需要插入数据时,系统直接查询这个“地图”,就能快速找到一个有空位的 Page ,而无需进行代价高昂的全表扫描。

    • 记录空闲 Page

      记录哪些 Page 是完全空的,可以被快速分配

    • Page 类型(Page type)

      记录 Page 是存储用户数据,存储(Meta-data) ,还是存储索引(index)

Page Directory 麻烦的地方在于需要确保 Page Directory 和 Data Page 保持同步,通常在硬盘里,Page Directory 不需要实时同步,这样可以大幅提高性能。

每个 Page 都会有一个 Header,用来存放这个 Page 的 Metadata。

Page Layout

数据库通常有两种存储方式:

  1. 行式存储
  2. 列式存储

但是行式存储是更为常见的。

数据库表通常有三种存储方式:

  1. Tuple-oriented Storage
  2. Log-structured Storage
  3. Index-organized Storage

Tuple-oriented Storage

Tuple-oriented Storage 的存储方式就是将数据一行行完整的保存下来。
但是这种存储方式有一个问题,就是当存储了多条数据,中间的数据被删掉后,再添加新的数据时,如果想要添加到被刚刚删除的空位中,需要遍历整个数据库,这是一个非常慢的操作。

Slotted Pages

Slotted Page 是用来解决刚刚提到的问题的技术。
Slotted Page 的原理就是,在 Page 的开头,使用一个 Header , Header 中有一个固定大小的数组,它会取映射 tuples 的起始位置和偏移量。

但是 Slotted Page 也有缺点,就是当写入的数据都是小数据时,会出现 Header 的数组已经被写满了,但是 Page 还有空间的情况。

Slotted Page 最大的优点在于,当需要移动数据位置的时候,只需要更新 Header 中的数组,不需要取更新其他的东西。

Tuple Layout

从本质上来说 Tuple 就是一堆字节,每个 Tuple 拥有一个 Header 。

Tuple Header

Tuple Header 会包括一些关于 Tuple 的 Metadata,例如:可见性,空值。

Tuple Data

它会根据表的字段进行顺序存储。

Word-Alignment: Padding

现在的 CPU 大多都是 64-bits ,每次读取数据也会读取 64-bits ,当数据的长度小于 64-bits 时,就有可能读取到相邻字段的数据,为了解决这个问题通常会使用进行填充对齐。

Tuple 的不同字段长度不同,为了方便系统的读取,通常会使用 64-bits 作为每个字段的长度,如果一个字段类型不满 64-bits,就会使用 0 进行填充用于对齐。

这样虽然会浪费空间,但是它能确保不读取到错误的数据。

Null Data Types

空值处理通常有三种方法:

  1. 在 Tuple Header 中定义,不需要管数据是否真的为空, Header 定义为空就是空。

这也是最常用的方法

  1. 使用一个特殊的值以表示空值,然后不允许插叙该值。

这种方法在列存储中比较常见,这是一种较为罕见的方法,缺点是每种类型都有一个值不能使用

  1. 为每个值专门设置一个属性,用于判断是否为空值。

非常弱智,不要使用

Large Values

大多数数据库不允许 Tuple 超过单个 Page 的范围。

如果想要存储大于一个 Page 的值,通常会存储一个指针,用于指向一个新的 Page 。

External Value Storage

如果使用数据库存入一个大文件,会出现 BLOB(Binary Large Object) 问题,数据会被切分成很多个块,存在多个 Page 里,这会给 Buffer Pool 造成巨大的压力,而且性能十分低,非常弱智。

当数据库需要保存一个超大的东西时,例如一部 20GB 的电影,更好的做法是将文件存在文件系统中,然后将文件系统的指针存入数据库中。
但是这种解决办法有缺点:

  • 数据库不能管控
  • 没有事务的保护