数据库导论#4

CMU 15-445 数据库系统导论(Fall 2024)课程笔记

一、课程安排与外部活动

1. 作业与项目进度

  • 已截止任务:作业一(Assignment 1)和项目零(Project 0)于课程前一晚到期,项目零整体完成质量较好。
  • 项目一(Project 1)发布:计划当天或次日发布,需同步上传源代码、教程、排行榜及完整文档;项目基于 “内存管理” 相关讲座(需实现自定义缓冲区),可能在周三简要讨论,或推迟至下周。

2. 外部讲座与技术活动

活动主题时间地点 / 福利备注
Databricks 技术讲座次日 18:00盖茨大楼内容围绕 Mosaic LLM,提供食物
Snowflake 技术讲座本周四 12:009 层提供比萨,主讲人含课程往届学生
Influx 数据融合研讨会第 23 天(周一)-属于课程研讨会系列,主题为 “构建块”
MDavis 集团校园活动下周周一、周二校园内面向本课程学生开放,含研究讲座、海报展

3. 企业招聘与交流

  • 信息会议:下周周二,各赞助公司(除 Confluent 外)举办 1 小时信息会,介绍业务、实习及全职岗位。
  • 简历投递:需将简历提交至 Piazza 发布的电子表格,课程团队将同步给所有企业。
  • 预约交流:通过 Piazza 报名表选择与企业的会面时间。

二、数据库存储架构核心

1. 磁盘导向数据库:面向元组的架构(Tuple-Oriented Architecture)

(1)核心设计假设
  • 数据主要存储于非易失性存储设备(磁盘),系统需处理 “数据不在内存、需从磁盘加载” 的场景,且无法在磁盘上原地更新元组(需加载至内存修改后写回)。
  • 核心是面向元组的组织方式:数据库管理系统(DBMS)需跟踪元组的位置(“元组在哪一页、页内有哪些元组”),基于 “堆文件(Heap File)” 存储无序元组。
(2)插槽页(Slotted Page)方案

是面向元组架构的核心页面组织方式,通过 “间接层” 实现元组灵活管理,结构如下:

  1. 页面标题(Header):存储页面元数据(如校验和、空闲空间大小、插槽数组长度)。
  2. 插槽数组(Slot Array):从页面起始位置向末尾增长,每个插槽存储元组在页内的偏移量(支持固定 / 可变长度元组)。
  3. 元组数据区:从页面末尾向起始位置增长,与插槽数组 “相向而行”,页面满时两者相遇。
  • 核心优势:间接引用(通过插槽号而非直接偏移量定位元组)。若需重新组织页内元组(如删除元组后回收空间),仅需更新插槽数组的偏移量,无需修改外部引用(如索引),降低维护成本。
  • 代价:访问元组需多一步 “插槽数组查找”,但相比磁盘 IO 可忽略。
(3)记录 ID(Record ID / RID):元组的物理地址
  • 定义:代表元组在数据库中的物理位置,是 DBMS 内部用于定位元组的 “坐标”,而非应用层的逻辑键(如主键)。
  • 组成:通常为 “文件 ID + 页面 ID + 插槽号”(不同 DBMS 略有差异),示例如下:
数据库系统Record ID 形式特点删除 / 插入处理逻辑
Postgres(页面 ID, 插槽号)插槽号从 1 开始;删除元组后留空槽,需通过VACUUM FULL(垃圾回收)重新利用空间插入新元组默认追加至末尾,VACUUM后紧凑页面
SQLite单调递增的行 ID(Row ID)作为默认合成主键,基于 B + 树索引定位元组删除后不重用 ID,无 “真空” 机制,无法紧凑页面
SQL Server十六进制物理地址(含文件 ID、页 ID、插槽号)删除元组后自动紧凑页面,重用空闲插槽插入新元组优先填充空闲槽,无槽时追加至末尾
Oracle字节数组(含对象 ID、文件号、块号、插槽号)插槽号从 0 开始;删除后不紧凑页面插入新元组默认追加至末尾
  • 注意:应用层不应依赖 Record ID,因关系模型中表数据无序,元组可能被移动(如 Postgres 的VACUUM、SQL Server 的紧凑),导致 Record ID 变化。
(4)面向元组架构的问题
  1. 碎片化:删除元组后若不紧凑(如 Postgres 默认行为),页内会产生空闲槽,导致页面利用率低。
  2. 磁盘 IO 浪费:访问单个元组需加载整个页面(磁盘 IO 粒度为页面),更新 10 个分散在不同页的元组需读取 10 个页面,存在大量无用数据加载。
  3. 不兼容非原地更新设备:SSD 需覆盖整个区块、分布式文件系统(如 HDFS、S3)仅支持追加写,无法支持 “加载 - 修改 - 写回” 的原地更新逻辑。

2. 日志结构存储(Log-Structured Storage / LSM Tree)

为解决面向元组架构的缺陷(IO 浪费、不支持追加写设备)而设计,核心是 “用顺序追加写替代随机更新”,适用于写入密集型场景。

(1)核心思想
  • 不直接更新元组,而是将所有修改(插入、删除、更新)记录为日志条目,通过 “内存表暂存 + 磁盘表持久化” 的分层结构优化读写性能。
  • 关键假设:内存中可快速进行原地更新,磁盘仅支持顺序追加写;通过 “后台压缩” 合并冗余日志,保证读取效率。
(2)核心组件与流程
  1. 内存表(MemTable)
    • 驻留内存的有序数据结构(如跳表、B + 树),用于接收最新的写操作(插入、删除、更新),支持快速键值查找和原地更新。
    • 当 MemTable 达到容量阈值(如几百 MB),会 “冻结” 并异步写入磁盘,转为SSTable(Sorted String Table),同时创建新 MemTable 接收新写操作。
  2. SSTable(Sorted String Table)
    • 磁盘上的有序 immutable 文件(仅追加、不修改),内部按键(Key)排序,存储 “键 - 值(元组)” 或 “键 - 删除标记(墓碑)”。
    • 按 “层级(Level)” 组织:新生成的 SSTable 放入 Level 0,随着 Level 内 SSTable 数量达到阈值,触发 “压缩(Compaction)” 合并到更高 Level(Level 1、Level 2…),Level 越高,SSTable 越大、数量越少。
  3. 压缩(Compaction)
    • 目的:合并同一 Level 或相邻 Level 的 SSTable,删除冗余条目(如旧版本元组、已删除元组的墓碑),减少磁盘占用并优化读取。
    • 过程:基于 “排序合并算法”—— 因 SSTable 已按键排序,通过迭代器遍历多个 SSTable,比较键值,保留最新版本的条目(时间戳或 Level 先后判断新旧),生成新 SSTable 写入更高 Level。
  4. 读取流程
    • 优先查 MemTable(最新数据);若未命中,按 Level 从低到高(Level 0 → Level 1 → …)查 SSTable。
    • 优化手段:
      • 总结表(Summary Table):存储每个 SSTable 的 “最小键 - 最大键”,快速判断键是否在该 SSTable 范围内。
      • 布隆过滤器(Bloom Filter):为每个 Level 或 SSTable 构建布隆过滤器,快速排除 “键不存在” 的情况,避免不必要的磁盘 IO。
(3)优缺点与实际应用
优点缺点典型应用
写入快(顺序追加,无随机 IO)读取慢(需遍历多 Level SSTable)RocksDB(Facebook,基于 LevelDB)
兼容仅追加写设备(HDFS、S3)写放大(元组生命周期中被多次重写)Cockroach DB、Neon(改造 Postgres 存储层)
后台压缩不阻塞前台写操作压缩开销大(CPU/IO 密集)分布式数据库、键值存储

3. 索引组织存储(Index-Organized Storage)

与 “堆文件 + 独立索引” 的模式不同,索引本身即存储—— 索引的叶子节点直接存储元组,无需通过 Record ID 定位元组,适用于读取密集型场景。

(1)核心设计
  • 基于有序索引(如 B + 树)组织元组:内部节点为 “路标”(存储键范围,指引查找方向),叶子节点按键排序并直接存储完整元组(而非 Record ID)。
  • 页面结构:叶子节点页面类似 “有序插槽页”—— 插槽数组按键排序,元组数据区也按键顺序存储,支持二分查找快速定位元组。
(2)查找流程
  1. 基于查询键遍历索引(如 B + 树),定位到对应的叶子节点页面。
  2. 在叶子节点的插槽数组中二分查找目标键,直接通过偏移量获取元组(无需额外查找堆文件)。
(3)适用场景与数据库
  • 适合 “按主键频繁查找” 的场景(如用户表按用户 ID 查询),避免 “索引查找→Record ID→堆文件” 的两步操作。
  • 支持数据库:SQLite(默认 Row ID 索引组织)、MySQL(InnoDB 引擎默认聚簇索引)、Oracle(可选 “索引组织表” 模式)、SQL Server(支持聚簇索引)。

三、元组的内部结构

元组本质是有序字节序列,DBMS 通过 “结构定义” 赋予字节意义,核心组成如下:

  1. 元组头部(Tuple Header)
    • 存储元组元数据,如:
      • 元组长度、属性数量;
      • 空值标记(如位图,标记哪些属性为 NULL);
      • 事务相关信息(如版本号,用于多版本并发控制)。
    • 头部大小固定(不同 DBMS 略有差异),确保属性数据区偏移量可计算。
  2. 属性数据区
    • 按表定义的属性顺序存储字节,如 “32 位整数 ID + 64 位整数 Value” 的元组,数据区为 “4 字节 ID + 8 字节 Value”。
    • 关键问题:数据对齐—— 现代 CPU(64 位)要求数据在 “字长边界”(如 8 字节)对齐,否则可能触发性能损耗或错误。若表中混合短类型(如 1 字节布尔值)和长类型(如 8 字节浮点数),需通过 “填充字节” 保证对齐。

四、关键概念总结

概念核心定义核心作用
插槽页(Slotted Page)面向元组架构的页面组织方式,含标题、插槽数组、元组数据区灵活管理页内元组,减少外部引用维护
Record ID元组的物理地址(文件 ID + 页 ID + 插槽号)DBMS 内部定位元组的核心坐标
LSM Tree日志结构存储的实现,含 MemTable、SSTable、Compaction用顺序写优化写入密集场景
索引组织存储索引叶子节点直接存储元组,索引即存储优化读取密集场景,减少查找步骤
数据对齐元组属性数据按 CPU

问答

以下 10 道问答题围绕课程笔记核心知识点设计,涵盖数据库存储架构、关键组件原理、不同系统特性等,答案严格对应笔记内容,突出核心逻辑与细节:

1. 插槽页(Slotted Page)方案中,“间接引用” 通过什么实现?这种设计的核心优势是什么?

答案

  • 间接引用通过 “插槽数组” 实现:插槽数组存储元组在页内的偏移量,访问元组时需先查插槽数组获取偏移量,再定位元组数据。
  • 核心优势:重新组织页内元组(如删除后回收空间)时,仅需更新插槽数组的偏移量,无需修改外部引用(如索引),降低维护成本;支持固定长度和可变长度元组的灵活管理。

2. 不同数据库系统的 Record ID(记录 ID)形式存在差异,请对比 Postgres 和 SQL Server 在删除元组后,Record ID 对应的插槽处理逻辑有何不同?

答案

  • Postgres:Record ID 为 “页面 ID + 插槽号”(插槽号从 1 开始),删除元组后会保留空插槽,不自动紧凑页面;需手动执行VACUUM FULL(垃圾回收)才能重新利用空插槽,插入新元组默认追加至页面末尾。
  • SQL Server:Record ID 为含文件 ID、页 ID、插槽号的十六进制地址,删除元组后会自动紧凑页面(将后续元组前移),重用空闲插槽;插入新元组时优先填充空闲槽,无空闲槽则追加至末尾。

3. 日志结构存储(LSM Tree)中的 MemTable 和 SSTable 分别是什么?MemTable 何时会转换为 SSTable?

答案

  • MemTable:驻留内存的有序数据结构(如跳表、B + 树),用于接收最新写操作(插入、删除、更新),支持快速键值查找和原地更新,容量通常为几百 MB。
  • SSTable:MemTable 达到容量阈值后 “冻结” 并异步写入磁盘的 immutable 文件,内部按键排序,存储 “键 - 元组” 或 “键 - 删除墓碑”,按 “层级(Level)” 组织。
  • 转换条件:当 MemTable 的容量达到预设阈值(如填满内存分配的空间),会冻结当前 MemTable 并生成新 MemTable 接收新写操作,冻结后的 MemTable 异步写入磁盘成为 Level 0 的 SSTable。

4. LSM Tree 中的 “压缩(Compaction)” 操作是什么?触发压缩的条件和核心目的是什么?

答案

  • 压缩操作:将同一 Level 或相邻 Level 的多个 SSTable(均按键排序)通过 “排序合并算法” 合并为新 SSTable,保留键的最新版本(按时间戳或 Level 先后判断),删除冗余条目(旧版本元组、删除墓碑)。
  • 触发条件:当某 Level 的 SSTable 数量达到阈值(如 Level 0 最多存 4 个 SSTable),触发该 Level 与更高 Level 的部分 SSTable 合并,合并后的新 SSTable 存入更高 Level。
  • 核心目的:① 减少磁盘占用(删除冗余数据);② 优化读取性能(减少需遍历的 SSTable 数量);③ 维持 LSM 的层级结构,避免低 Level SSTable 过多导致读取效率下降。

5. 面向元组的架构(Tuple-Oriented Architecture)和日志结构存储(LSM Tree)分别适用于什么场景?请说明原因。

答案

  • 面向元组的架构:适用于读取密集、更新分散的场景。
    原因:基于堆文件和插槽页,元组一旦写入磁盘(忽略碎片化处理),生命周期内无需重复写入,无 “写放大” 问题;但更新需加载整个页面,随机 IO 较多,写入效率低。
  • 日志结构存储:适用于写入密集、读取可接受一定延迟的场景(如分布式数据库、键值存储)。
    原因:通过 MemTable 暂存写操作、SSTable 顺序追加写,避免随机 IO,写入速度快;但读取需遍历多 Level SSTable(依赖布隆过滤器和总结表优化),且压缩会导致 “写放大”(元组多次重写)。

6. 索引组织存储(Index-Organized Storage)与 “堆文件 + 独立索引” 的模式相比,核心区别是什么?这种区别带来了什么优势?

答案

  • 核心区别:索引组织存储中,索引的叶子节点直接存储完整元组;而 “堆文件 + 独立索引” 中,索引叶子节点仅存储元组的 Record ID,需通过 Record ID 到堆文件中查找元组。
  • 优势:减少查找步骤(无需 “索引→Record ID→堆文件” 的二次定位),显著提升 “按索引键查找元组” 的效率,尤其适合按主键频繁查询的场景(如用户表按用户 ID 查询)。

7. Postgres 中,删除元组后为何会出现 “页面碎片化”?如何解决这一问题?

答案

  • 碎片化原因:Postgres 的插槽页默认不自动紧凑页面 —— 删除元组后,仅标记对应插槽为 “空闲”,不移动其他元组填充空闲空间,导致页内出现 “空槽”,页面利用率降低(即碎片化)。
  • 解决方法:执行VACUUM FULL命令(Postgres 的垃圾回收机制):① 扫描表的所有页面,收集可见元组(排除已删除、过期版本的元组);② 创建新页面存储这些可见元组,按顺序排列(无空槽);③ 释放原页面的碎片化空间,实现页面紧凑。

8. 在 LSM Tree 的读取流程中,如何避免遍历所有 Level 的 SSTable?依赖哪些优化组件?

答案

  • 避免全遍历的核心逻辑:按 “从新到旧” 的顺序查找(先查 MemTable,再查 Level 0→Level 1→…),找到键的最新版本后立即返回,无需继续遍历更高 Level。
  • 关键优化组件:
    ① 布隆过滤器(Bloom Filter):为每个 Level 或 SSTable 构建,快速判断 “键是否可能存在于该 SSTable”,若判断 “不存在” 则直接跳过该 SSTable/Level,避免无效磁盘 IO;
    ② 总结表(Summary Table):存储每个 SSTable 的 “最小键 - 最大键”,快速判断键是否在该 SSTable 的键范围内,若不在则跳过。

9. 元组内部结构中的 “数据对齐” 是什么?为何数据库系统需要关注数据对齐?

答案

  • 数据对齐:指元组的属性数据按 CPU 的 “字长边界”(如 64 位 CPU 的 8 字节边界)存储,即属性数据的起始地址是字长的整数倍。
  • 关注原因:现代 CPU 仅高效支持对齐数据的访问 —— 若数据未对齐,CPU 需执行额外操作(如分两次读取再拼接),导致性能损耗;严重时(如某些硬件架构)可能直接触发访问错误,因此 DBMS 需通过 “填充字节” 调整属性位置,确保数据对齐。

10. SQLite 的 Row ID 和 Oracle 的 Row ID 在形式和特性上有何差异?

答案

对比维度SQLite 的 Row IDOracle 的 Row ID
形式单调递增的整数(如 1、2、3…)字节数组(含对象 ID、文件号、块号、插槽号)
核心特性作为默认合成主键,基于 B + 树索引定位元组;删除元组后不重用 ID,无 “真空” 机制代表元组的物理位置,插槽号从 0 开始;删除元组后不紧凑页面,新元组默认追加
应用层依赖风险不建议依赖(元组删除后 ID 不重用,可能出现间隙)不建议依赖(元组移动后 Row ID 可能变化)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值