CMU15-445 Lecture #03: Database Storage (Part I)

课程链接:15-445/645 Database Systems (Fall 2020)

本文由 nefu-ljw 翻译于Notes:https://15445.courses.cs.cmu.edu/fall2020/notes/03-storage1.pdf

所有Notes已同步更新于我的github仓库:https://github.com/nefu-ljw/database-notes

目录

1. Storage

我们将重点介绍面向磁盘的(disk-oriented) DBMS体系结构,该体系结构假定数据库的主存储位置位于非易失性磁盘上。

在存储层次结构的顶部,您拥有距离CPU最近的设备。这是最快的存储,但也是最小和最贵的。您离CPU越远,存储设备就越大,但速度越慢。这些设备的每GB价格也变得更低。

易失性设备(内存):

  • 易失性意味着如果您从机器上拔下电源,那么数据就会丢失。
  • 易失性存储支持字节可寻址位置的快速随机访问。这意味着程序可以跳转到任何字节地址并获取那里的数据。
  • 出于我们的目的,我们始终将此存储类称为内存(Memory)。

非易失性设备(磁盘):

  • 非易失性意味着不需要为存储设备提供连续的电源,以便该设备保留其正在存储的位(bits)。
  • 它也是块/页可寻址的。这意味着,为了读取特定偏移量上的值,程序首先必须将4KB页加载到保存程序要读取的值的内存中。
  • 非易失性存储传统上更擅长顺序访问(同时读取多个连续的数据块)。
  • 我们将其称为磁盘(disk)。我们不会对固态存储(SSD)和旋转硬盘(HDD)进行主要区分。

还有一种新的存储设备正在变得越来越流行,称为持久内存(persistant memory)。这些设备被设计成两全其美:速度几乎与DRAM一样快,并且具有磁盘持久性。我们不会在本课程中介绍这些设备。

因为系统假设数据库存储在磁盘上,所以DBMS的组件负责确定如何在非易失性磁盘和易失性存储器之间移动数据,因为系统不能直接操作磁盘上的数据。

我们将关注如何隐藏磁盘的延迟,而不是关注寄存器和缓存的优化,因为从磁盘获取数据非常慢。如果从L1缓存引用读取数据需要半秒,则从SSD读取将需要1.7天,从HDD读取将需要16.5周。

2. Disk-Oriented DBMS Overview

数据库都在磁盘上,数据库文件中的数据被组织成页,第一页是目录页。为了对数据进行操作,DBMS需要将数据放入内存。它通过使用缓冲池来管理数据在磁盘和内存之间的来回移动来实现这一点。DBMS还具有执行查询的执行引擎。执行引擎将向缓冲池请求特定页面,而缓冲池负责将该页面放入内存,并为执行引擎提供指向内存中该页面的指针。 缓冲池管理器将确保在执行引擎对该部分内存进行操作时页面在那里。

3. DBMS vs. OS

DBMS的高级设计目标是支持超过可用内存量的数据库。由于读/写磁盘的开销很大,因此必须小心管理。我们不希望从磁盘获取数据时出现较大的停顿,从而降低其他所有操作的速度。我们希望DBMS在等待从磁盘获取数据时能够处理其他查询。

这个高级设计目标类似于虚拟内存,其中有很大的地址空间和供操作系统从磁盘引入页面的位置。

实现这种虚拟内存的一种方法是使用mmap将文件内容映射到进程的地址空间,这使得操作系统负责在磁盘和内存之间来回移动页面。不幸的是,这意味着如果mmap遇到页面错误,进程将被阻塞。

  • 如果你需要写入,你永远不会想要在DBMS中使用mmap。
  • DBMS(几乎)总是希望自己控制事物,并且可以做得更好,因为它更了解正在访问的数据和正在处理的查询。
  • 操作系统不是你的朋友。

可以通过以下方式使用操作系统:

  • madvise: 告知操作系统知道您何时计划读取某些页面。
  • mlock: 告知操作系统不要将内存范围向外交换到磁盘。
  • msync: 通知操作系统将内存范围刷新到磁盘。

出于正确性和性能原因,我们不建议在DBMS中使用mmap。

尽管DBMS的功能看起来像是操作系统可以提供的功能,但让DBMS自己实现这些过程会给它带来更好的控制和性能。

4. File Storage

在其最基本的形式中,DBMS将数据库存储为磁盘上的文件。有些可能使用文件层次结构,有些可能使用单个文件(例如SQLite)。

操作系统对这些文件的内容一无所知。只有DBMS知道如何解密它们的内容,因为它是以特定于DBMS的方式编码的。

DBMS的存储管理器(storage manager)负责管理数据库的文件。它将文件表示为页面集合。它还跟踪哪些数据已被读取和写入页面,以及这些页面中有多少可用空间。

5. Database Pages

DBMS在一个或多个文件(称为pages的固定大小的数据块)中组织数据库。页面(pages)可以包含不同类型的数据(元组、索引等)。大多数系统不会在页面中混合使用这些类型。一些系统将要求页面是自包含的(self-contained),这意味着阅读每个页面所需的所有信息都在页面本身上。

每个页面都有一个唯一的标识符。如果数据库是单个文件,那么页面id可以只是文件偏移量。大多数DBMS都有一个间接层,它将页面id映射到文件路径和偏移量。系统的上层将要求一个特定的页码。然后,存储管理器必须将该页码转换为一个文件和一个偏移量才能找到该页。

大多数DBMS使用固定大小的页面来避免支持可变大小页面所需的工程开销。例如,对于可变大小的页面,删除页面可能会在DBMS无法轻松用新页面填充的文件中造成漏洞。

DBMS中的页面有三个概念:

  1. 硬件页(通常为4KB)。
  2. 操作系统页(4 KB)。
  3. 数据库页(1-16KB)。

存储设备保证硬件页大小的原子写入。如果硬件页为4KB,并且系统尝试将4KB写入磁盘,则要么全部写入4KB,要么全部不写入。这意味着如果我们的数据库页大于我们的硬件页,DBMS将不得不采取额外的措施来确保数据被安全地写出,因为当系统崩溃时,程序可以中途将数据库页写入磁盘。

6. Database Heap

有几种方法可以找到DBMS想要的页面在磁盘上的位置,堆文件组织就是其中之一。

堆文件(heap file)是一个无序的页面集合,其中元组以随机顺序存储。

DBMS可以通过使用页面的链接列表(Linked List)或页面目录(Page Directory),在给定页面ID的情况下定位磁盘上的页面。

  • Linked List: 首页(header page)包含指向空闲页面列表和数据页面列表的指针。但是,如果DBMS正在查找特定的页,它必须对数据页列表进行顺序扫描,直到找到它要查找的页。
  • Page Directory: DBMS维护特殊的页面,这些页面跟踪数据页面的位置以及每页上的空闲空间量。

7. Page Layout

每个页面都包括一个数据头(header),该header记录有关页面内容的元数据:

  • 页面大小。
  • 校验和。
  • DBMS版本。
  • 事务可见性。
  • 自包含性。(有些系统如Oracle,需要这样做)

布局数据的一种strawman方法是跟踪DBMS在一个页面中存储了多少个元组,然后在每次添加新的元组时将其追加到末尾。但是,当删除元组或元组具有可变长度属性时,就会出现问题。

在页面中布局数据有两种主要方法:(1)分页布局(slotted-pages),(2)日志结构布局(log-structured)。

Slotted Pages:页面将槽(slots)映射到偏移量(offsets)。

  • 当今DBMS中最常用的方法。
  • Header跟踪使用的slot数量、最后使用的slot的起始位置的偏移量,以及slot数组(跟踪每个元组的起始位置)。

  • 要添加元组,slot数组将从头到尾增长,而元组的数据将从尾到头增长。当slot数组和元组数据相遇时,认为该页已满。

Log-Structured: DBMS只存储日志记录,而不存储元组。

  • 将数据库如何修改(插入、更新、删除)的记录存储到文件中。
  • 为了读取记录,DBMS向后扫描日志文件并重新创建(recreates)元组。
  • 写入速度较快,读取速度可能较慢。
  • 在仅附加(append-only)存储器上运行良好,因为DBMS不能返回和更新数据。
  • 为了避免长时间读取,DBMS可以使用索引来允许它跳转到日志中的特定位置。它还可以定期压缩日志(如果它有一个元组,然后对其进行更新,则可以将其压缩为只插入更新后的元组)。压缩的问题是DBMS最终会出现写入放大。(它会一遍又一遍地重写相同的数据)

8. Tuple Layout

元组本质上是一个字节序列。DBMS的工作是将这些字节解释为属性类型和值。

Tuple Header: 包含有关元组的元数据。

  • DBMS的并发控制协议的可见性信息(关于哪个事务创建/修改了该元组的信息)。
  • NULL值的位图(Bit Map)。
  • 注意,DBMS不需要在这里存储有关数据库模式(schema)的元数据。

Tuple Data: 属性的实际数据。

  • 属性通常按照您在创建表时指定的顺序存储。
  • 大多数DBMS不允许元组超过页面大小。

Unique Identifier:

  • 数据库中的每个元组都被分配了唯一标识符(unique identifier)。
  • 最常见的:page_id + (offset or slot)
  • 应用程序不能依赖这些ID来表示任何意义。

Denormalized Tuple Data: 非规格化元组数据。如果两个表是相关的,DBMS可以预先联接(pre-join)它们,因此这两个表最终会出现在同一页上。这使得读取速度更快,因为DBMS只需加载一个页面,而不是两个单独的页面。但是,由于DBMS需要为每个元组提供更多空间,这会使更新成本更高。