《数据库系统实现》第二版

《数据库系统实现(第2版)》内容深入且全面,技术实用且先进,叙述深入浅出,是一本难得的高层次的教材,适合作为高等院校计算机专业研究生的教材或本科生的教学参考书,也适合作为从事相关研究或开发工作的专业技术人员的高级参考资料。
  Hector Garcia-Molina,斯坦福大学计算机科学与电子工程系的Leona rd BoSack和SandraLerner教授。他在数据库系统、分布式系统和数字图书馆领域中发表了大量论文,研究兴趣包括分布式计算系统、数据库系统和数字图书馆。他是ACM会士、美国艺术与科学院会士和美国国家工程院成员。他在1999年获得了ACM SIGMOD创新奖。
  Jeffrey D.Ullman,斯坦福大学计算机科学与电子工程系StanfordW.Asche rman教授,数据库技术专家。他独立或与人合作出版了15.v.k著作,发表了170多篇技术论文,研究兴趣包括数据库理论、数据库集成、数据挖掘和利用信息基础设施进行教育。他是美国国家工程院成员,曾获得Knuth奖、SIGMOD贡献奖、Karlstrom杰出教育家奖DEdgar F.Codd发明奖。
  Jennifer Widom,美国康奈尔大学计算机科学博士,现为斯坦福大学计算机科学与电子工程系教授,研究兴趣包括半结构化数据的数据库系统问委员会的成员。她在2007年获得了ACM SIGMOD Edgar F.Codd发明奖。

0 关键字含义

关系:实际上是一张二维表,表的每一行是一个元素,每一列是一项属性。
元组:指的是一个关系上属性集的笛卡尔积的一个元素。大部分情况一下,我们可以理解为表的一行数据。

0 关系代数基本概念

五种基本操作:

并(Union):设关系R和关系S具有相同的属性n,且相应的属性取自同一个域,则关系R和关系S的并由属于R或属于S的元组组成,其结果仍为n元的关系

差(Difference):设关系R和关系S具有相同的属性n,且相应的属性取自同一个域,则关系R和关系S的差由属于关系R而不属于关系S的元组组成,其结果仍为n元的关系

笛卡尔积(Cartesian Product):设关系R和关系S的属性分别为r和s。定义R和S的笛卡尔积是一个(r+s)元的元组集合,每个元组的前r个分量来自R的一个元组,后s个分量来自S的一个元组

投影(Projection):对关系进行垂直分割,消去某些列,并重新安排列的顺序,再删去重复元组

选择(Selection):根据某些条件对关系做水平分割,即选择符合条件的元组

四种组合操作:

交(Intersection):设关系R和关系S具有相同的属性n,且相应的属性取自同一个域,则关系R和关系S的交由既属于关系R又属于关系S的元组组成,其结果仍为n元的关系。关系的交可以由关系的差来表示。

联接(Join):连接操作是笛卡尔积和选择操作的组合。

自然联接(Natural Join):是一种特殊的等值联接,它要求两个关系中进行比较的分量必须是相同的属性组,并且要在结果中把重复的属性去掉。

除(Division):设两个关系R和S的属性分别为r和s(设r>s>0),那么R除S是一个(r-s)元的元组的集合。它是满足下列条件的最大关系:其中每个元组t与S中的每个元组u组成的新元组必在关系R中。除运算是笛卡尔积的逆运算。

1 DBMS系统概述

数据模型三要素

  1. 数据结构:描述系统的静态特性
  • 数据本身
  • 数据之间的联系
  1. 数据操作:描述系统的动态特性,对数据库中对象的实例允许执行的操作的集合,包括操作及操作规则
  2. 完整性约束:完整性规则的集合,规定数据库状态及状态变化所满足的条件,保证数据库的正确、有效、相容

DBMS的主要功能

  1. 持久存储:支持对非常大量的数据进行存储,这些数据独立于使用数据的任何处理程序而存在
  2. 访问接口:使得用户可以通过强有力的查询语言访问数据和使用灵活的操作方式修改数据
  3. 事务管理:支持对数据的并发存取,多个不同的事务同时对数据进行存取并避免同时的访问可能造成的不良后果

DBMS的运行过程

image
  1. 用户向DBMS发出调用数据库数据的命令
  2. DBMS对命令进行语法检查、语义检查、存取权限检查,决定是否要执行该命令
  3. DBMS执行查询优化,把命令转化为一串串记录的存取操作序列
  4. 执行存取操作序列(反复执行以下各步直到结束)
  5. DBMS在缓冲区中查找记录,找到转10,没找到转6
  6. DBMS查看存储模式,决定从哪个文件存取哪个物理记录
  7. 根据6的结果,向操作系统发出读取记录的命令
  8. 操作系统执行读取数据的命令
  9. 操作系统将数据从数据库存储区送到系统缓冲区
  10. DBMS根据用户命令和数据字典的内容导出用户所要读取的数据格式
  11. DBMS将数据记录从系统缓冲区传送到用户工作区
  12. DBMS将执行状态信息返回给用户

2 辅助存储管理

概述

  1. 辅助存储负责管理的数据:包括目标数据、元数据、索引和日志等,这些数据保存在磁盘上。
  2. DBMS中改变了的数据必须写在非易失的磁盘上,才能认为改变的数据已成为数据库的一部分。

磁盘结构

磁盘结构

image

1. 圆盘-盘面-磁道-扇区 2. 磁头不于盘面接触,而是贴近地悬浮在盘面上,否则会发生头损毁,破坏盘片 3. 磁盘控制器的功能 + 定位磁头到一个特定的半径位置 + 选择一个准备读写的盘面,从位于该盘面的磁头下的磁道上选择一个扇区。并识别何时该扇区正开始移动到磁头下面。 + 将从该扇区读取的二进制位传送到主存储,或将从主存要写入的二进制位传送到该扇区。 + 为所写扇区附加校验和,并在读取扇区时检查它。 + 进行坏扇区的重映射

磁盘容量

  1. 磁盘容量=盘面×磁道×扇区×字节×8位
  2. 一个磁道多少块=磁道容量/扇区容量

磁盘访问时间

  1. 寻道时间:将磁头组合定位在磁盘块所在磁道的磁面上所需要的时间
  2. 旋转延迟(旋转等待时间):寻道结束后,读写头到等待被存取的扇区所需要的时间
  3. 传输时间:磁盘控制器读取或写数据时,数据所在扇区和扇区间的空隙经过磁头

磁盘的延迟=寻道时间+旋转延迟+传输时间

磁盘块存取的优化方法

  1. 才主存中对块进行缓冲以减少块的读写次数
  2. 按柱面组织数据
  3. 使用多个磁盘
  4. 磁盘镜像、

假定一个磁盘的MTTF是100,000小时,修复时间是10小时,则镜像磁盘系统的MTTF是100 , 00 0 2 100,000^2100,0002/(210)=5001 0 6 10^6106小时,约为57000年

  1. 磁盘臂调度——电梯算法
  2. 利用非易失性RAM作为写缓冲
  3. 预读和双缓冲
  4. 日志磁盘

RAID:廉价磁盘冗余阵列

作用

  1. 概述:利用大量廉价磁盘进行磁盘组织的技术
  2. 价格上:大量廉价的磁盘比少量昂贵的大磁盘合算
  3. 性能上: 使用大量的磁盘可以提高数据的并行存取
  4. 可靠性上: 冗余数据可以放在多个磁盘上,因而一个磁盘的故障不会导致数据丢失
  5. 通过冗余提高可靠性
  6. 通过并行提高性能
  7. 通过在多个磁盘上对数据进行拆分来提高传输率

分类与优缺点

RAIDO

  1. 将多个磁盘并列起来成为一个大磁盘
  2. 优点:
  • 速度最快
  • 块级拆分且没有任何冗余
  1. 缺点:如果一个磁盘损坏所有的数据都会丢失
  2. 应用:用于高性能访问并且数据丢失不十分重要的应用场合

RAID1

  1. 带块级拆分的磁盘镜像
  2. 优点:
  • 读性能好(是RAID0性能的两倍)
  • 写性能是由写性能最差的磁盘决定(但是对比以后的RAID来说写的速度还是较快的)
  • 可靠性很高
  1. 缺点:物理磁盘空间是逻辑磁盘空间的两倍
  2. 应用:用于类似于数据库系统中日志文件存储的应用场合

RAID2

  1. 按比特级拆分,具有内存风格的纠错码(ECC)
  2. 未被广泛应用,目前没有商业化产品
  3. 纠错码:内存中每个字节都有一个奇偶校验位与之相连,它记录这个字节中为1的比特位的总数是偶数(=0)还是奇数(=1),如果字节中有一位被破坏,则字节的ECC与存储的ECC就不会相匹配;通过ECC可以检测到所有的1位错误;通过更多的附加位,当数据遭到破坏时,还可以重建数据

RAID3

  1. 能够检测出一个扇区是否被正确的读出
  2. 优点
  • 效果与RAID2一样,但是只有一个磁盘的额外开销
  1. 缺点:
  • 恢复时间长
  • 每个磁盘参与每个IO请求,每秒RAID3支持的IO数较少

RAID4

  1. 特点:块级拆分,在一个独立的磁盘上为其他N个磁盘上对应的块保留一个奇偶校验位
  2. 优点:
  • 读取一个块只需要访问一个磁盘,RAID3需要访问所有的盘
  • 所有的磁盘都可以并行地读,读取大量数据的操作有很高的传输率
  1. 缺点:
  • 每个存取操作的传输率低,可以并行地执行多个读操作,因此产生较高的总的IO率

假定有4个数据盘和一个冗余盘
读数据:RAID3需要5次磁盘读操作;RAID4与从任何一个cip
写数据:RAID3需要3次磁盘读和两次磁盘写操作RAID4写数据需要两次磁盘读和两次磁盘写操作

RAID5(块交叉的分布奇偶校验

  1. 将数据和奇偶效验位第一分不到所有的N+1磁盘上,对每个块一个磁盘存储奇偶校验位其余磁盘存储数据
  2. 奇偶校验块不能和这个块对应的数据存储在同一个磁盘上
  3. 优点:
  • 包含了RAID4
  • 在相同的成本上,RAID5提供了更好的读写性能
image

RAID6

  1. 类似RAID5,存储了额外的冗余信息
  2. 不使用奇偶校验位的方法,使用Reed-Solomon码,对每4位数据存储两位冗余信息
  3. 优点:
  • 可以容忍两个磁盘发生故障(高可靠性)
image

RAID分别使用的技术

  1. RAID 0级:块级拆分,无冗余
  2. RAID 1级:带块级拆分的磁盘镜像
  3. RAID 2级:内存风格的纠错码组织结构
  4. RAID 3级:位交叉的奇偶校验组织结构
  5. RAID 4级:块交叉的奇偶校验组织结构
  6. RAID 5级:块交叉的分布奇偶校验位的组织结构
  7. RAID 6级:P+Q冗余方案

选择RAID级别应该考虑的因素

  1. 所需的额外磁盘存储带来的开销
  2. 在IO操作数量方面的性能需求
  3. 磁盘故障时的性能
  4. 数据重建过程中的性能

缓冲区

  1. 概述:主存中用于存储磁盘块的拷贝的部分,由固定数目的缓冲块构成
  2. 目的:减少磁盘与主存之间传输的快的数目
  3. 缓冲区管理器:负责缓冲区空间分配的子系统

缓冲区管理工作流程

image
  1. 请求处理的流程
  • 查看buffer pool是否包含此页,没有的则:
    • 找到一个pin_count(正在访问该frame的事务的个数)为0的frame,pin_count++
    • 如果dirty为true,写入磁盘
    • 将相应的页读入此frame
  • 将frame的地址返回

存储组织

  1. 页的格式:
  • 页是由一系列的记录构成的,每个记录为一个slot
  • 记录的id为=(page id, slot number)
  • 映射表:逻辑地址、物理地址
  1. 定长记录
  • 每条记录的长度是固定的,没有变长字段,一个页存放的数量和位置也是确定的
  1. 变长记录
  • 记录中包含变长字段,记录的长度是可变的,无法分配定长的slot
  • 用slot字典存放记录的起始位置和记录的长度,用记录在slot字典中的位置代替slot的实际起始位置

文件中记录的组织

堆文件组织

  1. 概述:一条记录可以存放在文件中的任何地方,只要有空间存放这条记录,记录是无序的,通常一个关系是一个单独的文件

顺序文件组织

  1. 记录根据搜索码的值顺序存储
  2. 大量插删改后需要重组:当进行大量的插删改后,搜索码顺序和物理顺序之间的一致性最终将完全丧失,在这种情况下,顺序处理将变得效率十分低下,此时文件应该被重组,使得他在物理上顺序存放,这种重组的代价是很高,而且必须在系统负载很低的时候执行。需要重组的频率依赖于新记录插入的频率

散列文件组织

  1. 在每条记录的某些属性上计算一个散列函数,散列函数的结果确定了记录应该放到文件的哪个快中

聚簇文件组织

  1. 几个不同关系的记录存储在同一文件中(通常用一个文件存储一个关系的记录),甚至用不同关系中的相关记录存储在相同的块中,于是一个IO操作可以从多个关系中取到相关记录

4 查询执行

SQL是关系模型操作的高层次抽象,所以SQL可以转化为一系列关系代数操作。执行关系代数操作的基本方法有扫描、散列、排序、索引等,这些方法对内存容量所做的假设也有所不同,一些算法假设内存可以容纳参与关系代数操作的数据对象,另外一些算法假设操作对象太大,内存无法容纳。这些算法在代价和结构上有明显的差别。

4.0 查询编译预览

查询编译可以分为三大步骤:

a) 分析,建立查询的分析树。
b) 查询重写,分析树被转化为初始查询计划,这种查询计划通常是查询的代数表达式。然后初始查询计划被转化为一个预期所需执行时间较小的等价查询计划,也被成为逻辑查询计划。
c) 物理计划生成,通常是通过给b中产出的逻辑查询计划的每一个操作符选择实现算法并选择这些操作符的执行顺序,逻辑计划被转化为物理查询计划。物理查询计划也是用表达式树来表示,同时还包含很多细节,如被查询的关系是怎样被访问的,以及一个关系何时或是否应该被排序。

b和c部分通常被称作查询优化器,它们是查询编译的难点。为了选择最好的查询计划,我们需要判断:

  1. 查询的哪一个代数等价形式会为查询带来最有效的算法。
  2. 对选中形式的每一个操作,应当使用什么算法实现。
  3. 数据如何从一个操作传到另一个操作。

这些选择都依赖关于数据库的元数据。

4.1 物理查询计划操作符介绍

物理查询计划由操作符构成,每一个操作符实现物理查询计划中的一步。物理操作符常常是一个关系代数操作的特定实现,除此之外,也有一些无关任务,例如扫描表,将关系代数要操作的某个关系的每个元组调入内存。

4.1.1 扫描表

读取一个关系R的整个内容,这个操作符的一个变体包含一个简单的谓词(仅读出关系R中满足这个谓词的元组)。

定位关系R中元组的基本方法

  1. 表-扫描,关系R大部分情况是存放在硬盘中,关系R中的元组排列存放在硬盘块中。系统知道包含关系R元组的块是哪些,并且可以一个接一个地读取这些块。
  2. 索引-扫描,如果关系R的任意属性上有索引,那么我们可以通过索引来得到R的所有元组,即使元组存放的块不是连续的。

4.1.2 扫描表时的排序

在读取一个关系的元组时,很多情况需要将关系排序。SQL中有 ORDER BY 语句会要求对关系排序,另外还有数据库关系代数操作的具体算法上也需要对关系进行排序(后面会讲到)。

排序-扫描的具体实现有多种方法,例如想产生关系R上按属性a排序的关系,假设a上有B-数索引或者R是按a排序的索引属性存储的,那么用索引扫描即可。假设关系R很小,则可以用表扫描,然后在内存中排序。假设关系R很大,那么后边会讲到用多路归并方法排序。

4.1.3 物理操作符计算模型

一个查询通常包括好几个关系代数操作,相应的物理查询计划由几个物理计划操作符组成。挑选最优的物理计划操作符需要预估每个操作符的代价。磁盘IO的数目是数据库衡量物理计划操作符代价的标准,这里有两个假设,第一操作符的对象位于硬盘、但结果在内存中,第二一个查询的中间操作结果也应该在内存中。

4.1.4 衡量代价的参数

我们设定,内存的一个缓冲区大小和硬盘块大小一致,我们用M表示缓冲区的数目

当描述一个关系R的大小时,绝大部分情况下,我们想知道的是关系R的所有元组所需要的硬盘块的数目。我们用B(R)B(R)表示聚集的关系R所占用的硬盘块数,简记为BB。

我们用T(R)T(R)表示关系R的元组总数,简记为TT,一个硬盘块能容纳的元组数表示为T/BT/B。

我们用V(R,a)V(R,a)表示关系R中a属性的不同值数目,以此可推,V(R,a1,a2…an)=T(R)V(R,a1,a2…an)=T(R)。

4.1.5 扫描操作符的IO代价

假设关系R是聚集的,那么表扫描操作符的代价近似为B,如果关系R能够全部装进内存,那排序扫描的代价也是B。

如果关系R不是聚集的,即元组分散在不同的硬盘块中,那么表扫描的代价就是T,如果关系R能够全部装进内存,那排序扫描的代价也是T。

4.1.6 实现物理操作符的迭代器

许多物理操作符可以实现为迭代器。迭代器有三个方法,这三个方法允许使用者一次获得一个元组。

  1. Open(),这个方法启动获取元组的过程,但并不获取元组,它用于初始化。
  2. GetNext(),这个方法返回结果中的下一个元组,并对数据结构做必要的调整以得到后续元组。调用对象通常循环调用该方法获取元组直到返回空。
  3. Close(),当使用者获取到所有元组,则需要调用该方法关闭数据连接。

使用迭代器的好处:同一时刻活跃的操作有很多,元组按照需要在操作符之间传递,这样就减少了存储要求。

表扫描的迭代器实现,在open方法中获取第一个块的第一个元组,在next方法中判断加载下一个块和元组。

排序扫描的迭代器实现,在open方法中读取整个关系R,然后排序,在next方法中顺序读取。

并操作的迭代器实现,在open方法中先调用第一个关系的迭代器,在next方法中判断第一个关系是否结束,如果结束就打开第二个关系的迭代器。

4.2 一趟算法

如何执行逻辑查询计划中的每个单独步骤(例如连接或选择)?逻辑查询计划转为物理查询计划的一个部分就是选择算法。大体上分为三类:

  1. 基于排序的方法
  2. 基于散列的方法
  3. 基于索引的方法

按照算法难度和代价分为三个等级:

  1. 一趟算法,仅从硬盘读取一次数据,大部分应用于操作对象能完全放入内存。
  2. 两趟算法,数据量太大,不能一次全部读入内存,但又不是特别大(后面会讨论什么是特别大)。算法特点是首先从硬盘读取一遍数据,按照某种方式处理完后再写入硬盘,之后在第二趟中读取数据进一步处理。
  3. 多趟算法,对处理的数据量大小没有限制,是对两趟算法的递归推广。

操作符的分类:

  1. 一次单个元组,一元操作。这类操作(选择σσ和投影ππ)不需要一次在内存中装入整个关系,这样可以一次读一个。
  2. 整个关系,一元操作。这些单操作对象需要一次从内存中看到全部元组。一趟算法局限于最大M个缓冲区的关系读取,一般是分组操作符γγ和去重操作符δδ。
  3. 整个关系,二元操作。交∩∩、并∪∪、差−−,连接和积的集合形式和包形式。要求这类操作的一个关系操作对象大小限制在M以内。

4.2.1 一次单个元组的一趟算法

非常简单,如果关系R是聚集的,那么IO代价是B。如果是非聚集的,代价是T。

有一个例外,带有在索引上属性和常量比较的选择扫描,效率会显著提高,

在open方法中非阻塞

4.2.2 整个关系的一元操作的一趟算法

消除重复

一次读取一个块,但对于每个元组要进行判断:

  1. 是第一个出现的元组,复制到缓冲区并输出。
  2. 以前见过,不用输出。

要求:B(δ(R))<=MB(δ(R))<=M

在open方法中非阻塞

分组

在内存中为分组创建一个项,在项中存有分组的属性值和聚集的一个或者多个累计值。

  • 对于MIN或MAX,只需要存一个最小值或最大值。
  • 对于COUNT,项内每见到一个元组加一。
  • 对于SUM,如果项内见到的被sum值不为NULL,则累加被sum值。
  • AVG情况复杂,需要保持两个累计值,个数和和。

这里需要注意,在open方法中,所有元组扫描完成后才能结束。

在open方法中阻塞

4.2.3 二元操作的一趟算法

交、并、差、积和连接操作。为了操作集合操作和包操作,这里用B和S分别表示包和集合。为了简化连接的讨论,仅考虑自然连接,θθ连接可以被认为是积或自然连接后额外增加的条件。

包并

包的并可以通过一种非常简单的一趟算法计算出来。R∪BSR∪BS,先复制关系R的元组到内存,再复制关系S的每一个元组到内存。IO代价为B(R)+B(S)B(R)+B(S),M=1M=1就够用。

在open方法中非阻塞

其他操作则需要将R和S中较小数量的关系读到内存并建立合适的数据结构。其内存要求是min(B(R),B(S))<=Mmin(B(R),B(S))<=M,以下我们假设S关系数据量最小。

集合并

将S读入内存,生成查找结构,Key为整个元组。然后一个一个地读取R的元组t,假如元组t在S中,就跳过,否则就输出。最后输出S的元组。

在open方法中非阻塞

集合交

将S读入内存,生成查找结构,Key为整个元组。然后一个一个地读取R的元组t,假如元组t在S中,就输出,否则就跳过。

在open方法中非阻塞

集合差

R−SSR−SS:将S读入内存,生成查找结构,Key为整个元组。然后一个一个地读取R的元组t,假如元组t在S中,就跳过,否则输出。

S−SRS−SR:将S读入内存,生成查找结构,Key为整个元组。然后一个一个地读取R的元组t,假如元组t在S中,那么删除内存中的元组t。处理完R的所有元组后,输出内存中剩余的元组。

在open方法中阻塞

包交

存储S的元组和元组出现的次数计数,注意,相同元组只存一份,计数加一。然后一个一个地读取R的元组t,假如元组t在S中,且计数不为0,则输出t并将计数减一。

在open方法中非阻塞

包差

S−BRS−BR:存储S的元组和元组出现的次数计数,注意,相同元组只存一份,计数加一。然后一个一个地读取R的元组t,假如元组t在S中,且计数不为0,则将计数减一。最后输出内存中剩余元组,输出次数为计数值。

R−BSR−BS:存储S的元组和元组出现的次数计数,注意,相同元组只存一份,计数加一。然后一个一个地读取R的元组t,假如元组t在S中,且计数不为0,则将计数减一,如果元组t不在S中或在S中且计数为0,则输出。

在open方法中阻塞

将S读入内存,不需要特殊结构。然后一个一个地读取R的元组t,与S的每一个元组连接并输出。

在open方法中非阻塞

自然连接

R(X,Y)R(X,Y)和S(Y,Z)S(Y,Z)自然连接,YY表示RR和SS的所有公共属性。

  1. 读取S的所有元组,并生成以Y为查找关键字的内存结构。
  2. 然后一个一个地读取R的元组t,判断t是否可以与S的元组连接,如果可以就连接输出。

在open方法中非阻塞

4.3 嵌套循环连接

在讨论更复杂的方法之前,先来看看嵌套循环连接操作算法。这些算法某种意义上来说需要一趟半。因为两个操作对象中的一个对象元组只用读取一次,而另一个操作对象的元组需要重复读取。

嵌套循环连接可以用于任何大小的关系。

在一趟算法的积和自然连接中,要求一个关系可以完全读入内存。而实际上,我们每个关系只读入一个元组,或者一块。

如果按块读取,那么块少的关系应该在循环外侧。例如B(R)=1000B(R)=1000,B(S)=500B(S)=500,M=100M=100,先循环关系R、再循环关系S所需读取块数为:10次外循环乘100+10次外循环乘5次内循环乘100=6000,先循环关系S、再循环关系R所需读取块数为:5次外循环乘100+5次外循环乘10次内循环乘100=5500。

4.4 基于排序的两趟算法

4.4.1 两阶段多路归并排序

假设我们有M个内存缓冲区进行排序,可以通过两趟的算法对非常大的关系进行排序,这种算法叫做两阶段多路归并排序(Two-Phase, Multiway Merge-Sort, TPMMS)

  • 阶段1:不断地将关系R中的元组放入M个缓冲区,利用内存排序算法对他们排序,并且将排序后的子表存入硬盘。
  • 阶段2:将排序好的子表进行归并。在这个阶段中,最多能对M-1个有序子表进行归并,这就限制了R的大小。

归并流程如下:

  1. 加载每个子表的第一块做缓冲块。
  2. 找到所有块中最小的元素移到输出缓冲区(只有一块)。
  3. 如果输出块已满,则将它写入硬盘新位置,并归零输出块。
  4. 如果被取出的最小元素所在块元素已耗尽,则取对应子表的下一块,如果子表中没有块,则保持该缓冲区为空。
  5. 调至第二步,直到所有缓冲区为空。

根据阶段1和阶段2可知,最多有M-1个子表,每个子表最多有M个块,所以关系R最多有(M−1)∗M(M−1)∗M个块,近似表示为M2M2。在整个过程中,需要读关系2两次,写关系1次。共计IO次数为3B。

例子:假设块大小为16KB,内存为1GB,那么M=1GB/16KB=16K,B最大为16K∗16K=22816K∗16K=228,总大小为4TB。

4.4.2 利用排序去重

在阶段2的归并流程2中,找到所有块中的最小元素并移到输出缓冲区,在这个操作上,先检查输出缓冲区是否有相同元组,如果有就忽略。

4.4.3 利用排序进行分组和聚集

在阶段1中,取分组属性作为排序关键字。在阶段2的归并流程2中,先判断是否有分组属性值相同的元组,有就做聚集操作,没有就直接输出。

4.4.4 基于排序的并算法

包并(4.2.3)算法与操作对象无关,但集合并算法与操作对象大小有关系。

在阶段1中,对关系R和S分别创建排序子表。在阶段2归并流程1中,为关系R和S的每个子表都使用缓冲区。在流程2中,在把元组t输入输出缓冲区后,删除输入缓冲区中和元组t相同的元组。

4.4.5 基于排序的交和差算法

算法和4.4.4节类似

  • 对于集合交:如果元组t在R和S中都出现,就输出t。
  • 对于包交:输出的t的次数是在R和S中出现的最小次数。
  • 对于集合差:关系R集合减S,当且仅当t出现在R中,但不在S中,就输出t。
  • 对于包差:关系R包减S,输出t的次数是t在R中出现的次数减去在S中出现的次数。

4.4.6 基于排序的一个简单连接算法

R(X,Y)和S(Y,Z)自然连接,Y表示R和S的所有公共属性。

  1. 用Y作为排序关键字,使用TPMMS对R进行排序。

  2. 对S也做排序。

  3. 对归并好的R和S,使用两个缓冲区。一个给R的当前块,一个给S的当前块。重复以下步骤:

  4. 在当前R和S的块找到Y的最小值y。

  5. 如果y在另一个关系中没有出现,那么就删除有关键字y的元组。

  6. 否则,找到两个关系中具有相关关键字y的所有元组。

  7. 输出通过连接R和S中具有共同y值的元组连接。

  8. 如果一个关系在内存中已没有要考虑的元组,就加载下一个元组。

  • 磁盘IO代价:5(B(R)+B(S))5(B(R)+B(S))
  • B(R)<=M∪B(S)<=MB(R)<=M∪B(S)<=M
  • 所有用于连接的一个值的对应所有元组必须能装入缓冲区

4.4.8 一种更有效的基于排序的连接

如果拥有公共值的元组太多,4.4.6算法就不可行。那么可以在排序的第二阶段和连接做合并。

  1. 用Y做关键字,对R和S生成排序子表
  2. 将每个子表的第一块调入缓冲区。
  3. 重复地在所有子表的最新元组中第一个查找最小值y。识别两个关系中具有y值的所有元组。输出这些元组的连接。如果有一个子表的缓冲区处理完毕,则重新将磁盘上的块装入其中。

4.5 基于散列的两趟算法

思想如下,如果数据量太大不能存储内存,就使用一个合适的散列关键字散列一个或多个操作对象的所有元组。使用该算法,能使我们把所有需要一起考虑的元组分配到相同的桶。

消除重复、分组和聚集、交并差、连接

4.6 基于索引的算法

非聚簇的关系不可能有一个聚簇的索引,但聚簇的关系可以有非聚簇的索引。

基于索引的选择。在没有索引的情况下磁盘IO为B或T。如果选择的属性在索引上,那么磁盘IO为B(R)/V(R,a)B(R)/V(R,a)。

使用索引的连接。磁盘IO最差情况为为T(R)T(S)/V(S,Y)T(R)T(S)/V(S,Y)、最好情况为T(R)B(S)/V(S,Y)T(R)B(S)/V(S,Y)

4.7 缓冲区管理

缓冲区管理策略

最近最少使用(LRU)

清空最长时间没有读写的块,这种方法要求保持一张缓冲区块的被访问最后一次时间的表。

先进先出(FIFO)

占用时间最长的块先被清空。B-数的根更容易被写回硬盘。

时钟算法(第二次机会)

该算法是LRU的最普遍的一个近似实现。实现方法是将缓冲区块看成一个环,每个块有一个标记(0或1,初始值为0),指针指向其中一个块。如果想读写某一个块,就把这个块的标志置为1。如果想找到一个可用的块,就顺时针旋转,找到第一个0,然后将其设置成1,在找的过程中扫过的块如果标志位1就将其标志设置成0。

4.8 使用超过两趟的算法

5 查询编译器

5.1 语法分析和预处理

5.1.1 语法分析和语法分析树

语法分析器接收类似SQL的文本,并转换为语法分析树。树的节点由以下两者构成:

  1. 原子:词法成分,例如关键字(select),关系或属性的名称,常数,括号,运算符,以及其他模式
  2. 语法类:在一个查询中的成分构成。例如表示select-from-where形式的查询,表示属于条件的表达式。

如果一个节点是原子,那么该节点没有子节点。如果一个节点是语法类,则其子节点通过该语言的语法规则进行描述。

5.1.2 SQL的简单子集语法

 ::= SELECT  FROM  WHERE 

 ::=  ,  

 ::= 

 ::==  , 

 ::== 

 ::==  AND 

 ::==  IN (  )

 ::==  = 

 ::==  like 


 为任意表示当前数据库模式的属性的字符串

 为当前模式中作为关系而言的字符串

 为任何用引号括起来的字符串

5.1.3 预处理器

虚视图替换,语义检查。

  1. 检查关系的使用(模式)。
  2. 检查和解析属性的使用(关系与属性)。
  3. 检查类型(筛选条件类型)。

5.2 用于改进查询计划的代数定律

5.2.1 交换律和结合律

积,连接,并,交都满足交换律和结合律。

交换律

  • R×S=S×RR×S=S×R

  • R⋈S=S⋈RR⋈S=S⋈R

  • R∪S=S∪RR∪S=S∪R

  • R∩S=S∩RR∩S=S∩R

  • (R×S)×T=R×(S×T)(R×S)×T=R×(S×T)

  • (R⋈S)⋈T=R⋈(S⋈T)(R⋈S)⋈T=R⋈(S⋈T)

  • (R∪S)∪T=R∪(S∪T)(R∪S)∪T=R∪(S∪T)

  • (R∩S)∩T=R∩(S∩T)(R∩S)∩T=R∩(S∩T)

5.2.2 涉及选择的定律

由于选择可以明显减少关系的大小,因此进行有效查询处理的最重要规则之一就是只要不改变表达式的结果,就把选择在语法树上尽可能的下移。

  • σC1ANDC2(R)=σC1(σC2(R))σC1ANDC2(R)=σC1(σC2(R))
  • σC1(σC2(R))=σC2(σC1(R))σC1(σC2(R))=σC2(σC1(R))
  • σC1ORC2(R)=σC1(R)∪σC2(R)σC1ORC2(R)=σC1(R)∪σC2(R)

涉及σσ的另一类定律允许我们对二元运算符进行下推选择:积、并、交、差、连接。有三中类型定律,这取决于下推选择到每个参数是可选的还是必须的。

  1. 对于并,选择必须下推到两个参数中。
  2. 对于差,选择必须下推到第一个参数,下推到第二个参数是可选的。
  3. 对于其他运算符,只要求选择下推到第一个参数。对于连接和积,将选择下推到两个参数是没有意义的,因为参数可能有也可能没有所要求的属性。即使可以下推到两者,该做法也不一定能改进计划。

因此对于并的定律是:

  • σC(R∪S)=σC(R)∪σC(S)σC(R∪S)=σC(R)∪σC(S)

对于差的定律:

  • σC(R−S)=σC(R)−SσC(R−S)=σC(R)−S
  • σC(R−S)=σC(R)−σC(S)σC(R−S)=σC(R)−σC(S)

下面这些定律允许将选择下推到一个或者两个参数,对于选择σCσC,我们只能将其下推到包含C所涉及的全部属性的关系中。假设关系R中有C提及的所有属性,有以下定律:

  • σC(R×S)=σC(R)×SσC(R×S)=σC(R)×S
  • σC(R⋈S)=σC(R)⋈SσC(R⋈S)=σC(R)⋈S
  • σC(R⋈DS)=σC(R)⋈DSσC(R⋈DS)=σC(R)⋈DS
  • σC(R∩S)=σC(R)∩SσC(R∩S)=σC(R)∩S

如果C只涉及S的属性,则有:

  • σC(R×S)=R×σC(S)σC(R×S)=R×σC(S)

对于其他3个运算符⋈⋈、⋈D⋈D和 ∩∩ 类似。如果关系R和S都包含C的属性,那么有诸如以下的定律:

  • σC(R⋈S)=σC(R)⋈σC(S)σC(R⋈S)=σC(R)⋈σC(S)

一些平凡的定律

  1. 任何对空关系的选择为空。
  2. 如果C总是为真的条件,则σC(R)=RσC(R)=R。
  3. 如果R为空,R∪S=SR∪S=S。

5.2.3 下推选择

5.2.2节的规则是查询优化器的有力工具,在包含虚视图时,需要先将选择尽可能往树的上部移,然后再把选择下推到所有可能分支。

5.2.4 涉及投影的定律

下推投影是有用的,但是一般而言不如下推选择那么有用,因为投影不改变元组数,只改变元组长度。

投影定律原理:

  • 可以在表达式树的任意位置引入投影,只要他所消除的属性是其上的运算符从来不会用到的,也不在整个表达式的结果之中。

5.2.5 有关连接和积的定律

  • R⋈CS=σC(R×S)R⋈CS=σC(R×S)
  • R⋈S=πL(σC(R×S))R⋈S=πL(σC(R×S))

5.2.6 有关消除重复的定律

  • δ(R×S)=δ(R)×δ(S)δ(R×S)=δ(R)×δ(S)
  • δ(R⋈S)=δ(R)⋈δ(S)δ(R⋈S)=δ(R)⋈δ(S)
  • δ(R⋈CS)=δ(R)⋈Cδ(S)δ(R⋈CS)=δ(R)⋈Cδ(S)
  • δ(σC(R))=σC(δ(R))δ(σC(R))=σC(δ(R))
  • δ(R∩BS)=δ(R)∩BS=R∩Bδ(S)=δ(R)∩Bδ(S)δ(R∩BS)=δ(R)∩BS=R∩Bδ(S)=δ(R)∩Bδ(S)

5.2.7 涉及分组和聚集的定律

  • δ(γL(R))=γL(R)δ(γL(R))=γL(R)
  • γL(R)=γ(πM(R))γL(R)=γ(πM(R)) ,其中,M包含L中提到的所有属性。
  • γL(R)=γL(δ(R))γL(R)=γL(δ(R)),当且仅当γLγL不受重复行影响(例如Min、Max等)

5.3 从语法分析树到逻辑查询计划

在5.1节,我们构造好了语法分析树,接下来需要把语法树转化为逻辑查询计划。

第一步,按适当的群组用一个或者多个关系代数运算符替代语法树上的节点和结构。第二步,利用第一步中产生的关系代数表达式,将其转换成我们所期待的可被转成最有效的物理查询计划的一个表达式。

5.3.1 转换成关系代数

select-from-where结构的关系代数的非正式陈述为:

如果我们有一个包含的没有子查询的,则可以用一个关系代数表达式替换整个成分——选择列表、from列表以及条件,其中代数表达式自底向上由下面这些内容组成:

  • 中提及的全部关系的积是以下运算符的参数。
  • 选择σCσC,其中C就是要被替换成分中表达式,同时又是下面运算符的参数。
  • 投影πLπL,其中L是中的属性列表。

5.3.2 从条件中去除子查询

对于中包含子查询的语法树,我们将引入运算符的中间形式,他介于语法分析树的语法类与作用到关系上的关系代数运算符之间。该运算符通常被成为两参数选择。我们将不带参数的标签为σσ的节点表示经转换后的语法树中的两参数选择。该节点之下有一个左子节点,它表示要对其做选择运算的关系R,以及一个右子节点,它表示作用到关系R的每个原则上的条件表达式。两个参数均可表示为语法树、表达式树或两者的混合。

选择条件的限制
为什么要去除子查询?选择σσ的条件实际上是针对每一个元组的筛选,即每拿出一个元组,都要执行一遍选择条件,判断满不满足。如果有子查询,则每拿出一个元组,就要执行一遍子查询,明显不现实。即使子查询与元组无关,那也对代价计算的影响很大。

规则的非正式描述,假设我们有一个两参数选择,其中第一个参数代表的关系R,第二个参数形如t in S,其中S是一个非相关子查询,t是R的元组。我们按照以下方式变换:

  1. 用S的表达式树替换,如果S有重复,则在S的表达式的根部增加δδ运算。
  2. 用单参数选择σCσC替换两参数选择,其中C是元组t和关系S中相应属性取等值条件。
  3. 给选择σCσC一个参数,他是R和S的积。

5.3.3 逻辑查询计划的改进

当我们把我们的查询语句转换为关系代数时,我们获得了一个可能的逻辑查询计划。下一步是在5.2节列出的代数定律上重写计划。

一下是优化器最常用到的:

  • 选择尽可能深地推入表达式树。如果一个选择条件是多个条件的AND,我们可以把该条件分解并分别将每个条件下推。
  • 投影下推。
  • 消除重复有时可以消去,或者移到树中更方便的未知。
  • 某些选择可以与下面的积相结合从而转为等值连接。

5.4 运算代价的估计

逻辑查询计划会对应多个物理查询计划,如何评价每个物理查询计划、或者估计实现的代价。通过以下选择进行代价枚举:

  1. 满足结合律和分配律的运算。
  2. 在逻辑计划中每个运算符的算法。
  3. 其他运算符。
  4. 参数从一个运算符传送到下一个运算符的方式。

为了做出每项选择,我们需要知道各个物理计划的代价是多少,在没有执行计划的前提下,我们不能准确地知道其代价。执行一个查询计划要比选一个计划所做的工作多,我们也不想同时执行多个查询计划。因此,我们必须能够预估某个计划的代价。

5.4.3 选择运算大小的估计

令S=σA=c(R)S=σA=c(R),有以下估计:

T(S)=T(R)/V(R,A)T(S)=T(R)/V(R,A)

如果S=σa>=10(R)S=σa>=10(R),有以下估计:

T(S)=T(R)/3T(S)=T(R)/3

S=σa!=10(R)S=σa!=10(R)我们认为取的是全量数据T(S)T(S)。

AND条件,建议是不同选择概率的乘积。

OR条件,大小难以估计,例如S=σC1orC2(R)S=σC1orC2(R)。当C1和C2相互独立,n为元组总数,m1表示满足C1的元组数,m2表示满足C2的元组数,有:

T(S)=n(1−(1−m1/n)(1−m2/n))T(S)=n(1−(1−m1/n)(1−m2/n))

5.4.4 连接运算大小的估计

三种情况:

  1. 没有相交的Y值,T(R⋈S)=0T(R⋈S)=0
  2. Y是S的主键,且是R的的外键,T(R⋈S)=T(R)T(R⋈S)=T(R)
  3. S和R的所有元组都有相同的Y值,T(R⋈S)=T(R)T(S)T(R⋈S)=T(R)T(S)

通常情况的估计:

T(R⋈S)=T(R)T(S)/max(V(R,Y),V(S,Y))T(R⋈S)=T(R)T(S)/max(V(R,Y),V(S,Y))

5.4.7 其他运算大小的估计

较大者加较小者的一半

较小者的一半

T(R)−T(S)/2T(R)−T(S)/2

消除重复

min(T(R)/2,×V(R,ai))min(T(R)/2,×V(R,ai))

分组和聚集

T(R)/2T(R)/2

5.7 物理查询计划选择的完成

经过分析查询,转化为初始的逻辑查询计划和优化。基于代价估计选择物理查询计划。将逻辑计划转化为完整地物理计划还需要以下这些内容:

  1. 执行计划的算法的选择。
  2. 中间结果何时被物化(存储在硬盘中)。
  3. 物理查询计划的运算符注释(包含存储关系的访问方法细节和相关代数运算的执行算法的细节)。

部分转自:http://blog.gavinzh.com/2019/06/29/database-system-implementation-lean/

你可能感兴趣的:(《数据库系统实现》第二版)