未验证 提交 0dd25aae 编写于 作者: 羽飞's avatar 羽飞 提交者: GitHub

Merge pull request #30 from longdafeng/issue29

fixed #29 add lecture to documents
# 版权声明
1. 本教材刊载的所有内容,包括但不限于文字报道、图片、视频、图表、标志标识、商标、版面设计、专栏目录与名称、内容分类标准等,均受《中华人民共和国著作权法》、《中华人民共和国商标法》、《中华人民共和国专利法》及适用之国际公约中有关著作权、商标权、专利权以及或其它财产所有权法律的保护,相应的版权或许可使用权均属华中科技大学谢美意老师、左琼老师所有。
2. 凡未经华中科技大学谢美意老师、左琼老师授权,任何媒体、网站及个人不得转载、复制、重制、改动、展示或使用《数据库管理系统实现基础讲义》的局部或全部的内容。如果已转载,请自行删除。同时,我们保留进一步追究相关行为主体的法律责任的权利。
3. 本教材刊载的所有内容授权给北京奥星贝斯科技有限公司。
# 数据库管理系统实现基础讲义
作者 华中科技大学谢美意 左琼
[第1章 数据库管理系统概述](lecture-1.md)
[第2章 数据库的存储结构](lecture-2.md)
[第3章 索引结构](lecture-3.md)
[第4章 查询处理](lecture-4.md)
[第5章 查询优化](lecture-5.md)
[第6章 事务处理](lecture-6.md)
[参考资料](references.md)
[版权声明](copyright.md)
# 第1章 数据库管理系统概述
## 1.1 课程简介
随着信息时代的发展,数据的重要性日益凸显,它是各级政府机构、科研部门、企事业单位的宝贵财富和资源,因此数据库系统的建设对于这些组织的生存和发展至关重要。作为数据库系统的核心和基础,数据库管理系统(Data Base Management System,DBMS)得到了越来越广泛的应用。DBMS帮助用户实现对共享数据的高效组织、存储、管理和存取,经过数十年的研究发展,已经成为继操作系统之后最复杂的系统软件。
对于DBMS的学习一般可分为两个阶段:
第一个阶段是学习DBMS的使用,包括如何运用数据库语言创建、访问和管理数据库,如何利用DBMS设计开发数据库应用程序。在这个阶段,学习者只需掌握DBMS提供的功能,并不需要了解DBMS本身的工作原理。
第二个阶段是学习DBMS的内部结构和实现机制。通过学习DBMS的实现技术,学习者对数据库系统的工作原理会有更深入的理解,这有助于学习者分析数据库系统在复杂应用环境中可能出现的各种性能问题,设计开发出更高效的数据库应用程序,并为其从事数据库管理软件和工具的开发及改进工作打下基础。
在本教程中,我们从DBMS开发者的视角,讨论实现一个关系型DBMS需要考虑的一些关键问题,比如:数据库在存储介质上是如何组织和存储的?一条SQL语句是如何被正确地解析执行的?有哪些结构和方法可以用来快速定位数据库中的记录,提高存取效率?多用户共享数据库时,如何在避免并发错误的同时提高并发度?发生故障时,如何保证数据库能够恢复到正确的状态?在此基础上,学习者可以尝试自己从零开始开发一个简单的DBMS,并逐渐完善、增强它的功能,在这个过程中掌握各种计算机专业知识在DBMS这样的复杂系统软件设计中的应用,提高自己的系统综合能力。
作为学习本课程的前提条件,我们假设学习者已经具备了一定的计算机学科背景知识,包括关系代数、关系数据库语言SQL、数据结构、算法,以及操作系统及编译的相关知识。
## 1.2 数据库管理系统的组成
![图1-1 DBMS内部结构图](images/1-1.png)
<center>图1-1 DBMS内部结构图</center>
DBMS允许用户创建数据库并对数据库中的数据进行查询和修改,同时提供故障时的数据恢复功能和多用户同时访问时的并发控制功能。图1-1是一个DBMS的内部结构示意图。其中单线框表示系统模块,双线框表示内存中的数据结构,实线表示控制流+数据流,虚线表示数据流。该图反映了DBMS的几大主要功能的处理流程,即数据定义、数据操纵和事务管理,这些功能均依赖底层的存储管理及缓冲区管理组件提供对磁盘中数据的访问支持。以下我们分别对这几个功能进行简要说明。
### 1.2.1 存储及缓冲区管理
数据库中的数据通常驻留在磁盘中,当系统需要对数据进行操作时,要先将其从磁盘读入内存。
存储管理器的任务是控制数据在磁盘上的放置和数据在磁盘与内存之间的交换。很多DBMS依赖底层操作系统的文件系统来管理磁盘中的数据,也有一些DBMS为了提高效率,直接控制数据在磁盘设备中的存储和访问。存储管理器登记了数据在磁盘上所处的位置,将上层模块提出的逻辑层面的页面访问请求映射为物理层面的磁盘访问命令。
缓冲区管理器将内存空间划分为与页面同等大小的帧,来缓存从磁盘读入的页面,并保证这些页面在内存和磁盘上的副本的一致性。DBMS中所有需要从磁盘获取信息的上层模块都需要与缓冲区管理器交互,通过缓冲区读写数据。这些信息包括以下类型:
- 数据:数据库自身的内容。
- 元数据:描述数据库的结构及其约束的数据库模式。
- 日志记录:记录事务对数据库所做修改的信息,用于保证数据库的一致性和持久性。
- 统计信息:DBMS收集和存储的关于表、索引等数据库对象的大小、 取值分布等信息,用于查询优化。
- 索引:支持对数据进行高效存取的数据结构。
### 1.2.2 DDL命令的处理
DDL是指数据定义语言,这类命令一般由DBA等有特殊权限的用户执行,用于定义或修改数据库的模式,比如创建或者删除表、索引等。关于数据库模式的描述信息称为元数据。元数据与普通数据一样,也是以表(称为系统表)的形式存在的。DDL命令由DDL处理器解析其语义,然后调用记录管理器及索引管理器对相应的元数据进行修改。
### 1.2.3 DML命令的处理
DML是指数据操纵语言,这类命令一般由普通用户或应用程序执行。DML又可分为对数据库的修改操作(增、删、改)和对数据库的查询操作。
对DML命令的处理中最重要的部分是查询处理。查询处理的过程分为以下几步:
- 查询分析及检查:先对查询语句的文本进行语法分析,将其转换为语法树,然后进行查询检查(例如,检查查询中所提到的关系是否确实存在),并将语法树中的某些结构转换成内部形式,形成查询树。查询树表示了一个关系代数表达式,即要在关系上执行的一系列操作。
- 查询优化:查询优化器利用元数据和关于数据的统计信息来确定哪个操作序列可能是最快的,将最初的查询树等价转换为最高效的操作序列。
- 查询执行:执行引擎负责查询计划的执行,它通过完成查询计划中的各个操作,得到最终的执行结果。在执行过程中,它需要与DBMS中很多其他组件进行交互。例如,调用记录管理器和索引管理器获取需要的数据,调用并发控制组件对缓冲区中的某条记录加锁以避免并发错误,或者调用日志组件登记对数据库所做的修改。
### 1.2.4 事务处理
事务是一组数据库操作,这组操作要么都做,要么都不做,不可分割。一个事务中包含哪些操作是由用户定义的,可以包含多个数据库操作,也可以只包含单个数据库操作。对事务的处理由事务管理器负责,它包括并发控制组件和日志及恢复组件,目的是保证事务的ACID特性,即原子性、一致性、隔离性和持久性。
事务管理器接收来自用户或应用程序的事务命令,从而得知什么时候事务开始、什么时候事务结束、以及事务的参数设置(例如事务的隔离级),然后在事务运行过程中执行下列任务:
- 登记日志:为了保证一致性和持久性,事务对于数据库的每一个修改都在磁盘上记录日志,以保证不管在什么时候发生故障,日志及恢复组件都能根据日志将数据库恢复到某个一致的状态。日志一开始被写到缓冲区中,然后会在适当的时机从日志缓冲区写回到磁盘中。
- 并发控制:事务的执行从表面上看必须是孤立的,但是在大多数系统中,实际上有许多事务在同时执行。因此,并发控制组件必须保证多个事务的各个动作以一种适当的顺序执行,从而使得最终的结果与这些事务串行执行的结果相同。常见的并发控制方式是封锁机制,通过加锁来防止两个事务以可能造成不良后果的方式存取同一数据。
## 1.3 关系模型和SQL
本教程讨论的关系型DBMS是以关系模型为理论基础的。另一方面,SQL作为一种关系数据库标准语言,得到了几乎所有商用关系DBMS的广泛支持。要实现一个关系DBMS,我们需要考虑如何在系统中支持符合关系模型定义的数据结构、数据操作和数据约束,同时支持用户通过SQL命令来访问系统。本节将简单回顾关系模型和SQL中的一些重要概念,并讨论二者的关系。
### 1.3.1关系模型
1970年,E.F.Codd在他的论文《A Relation Model of Data for Large Shared Data Banks》中首次提出关系模型。关系模型相对于层次模型和网状模型的优势在于:它提供了一种只使用自然结构来描述数据的方法,而不需要为了方便机器表示而附加任何额外的结构。这样就为更高级的数据语言提供了基础,这种语言使得程序能够独立于数据的机器表示及组织方式,具有更好的数据独立性。
#### 1.3.1.1关系
关系模型采用的数据结构称为关系。在关系模型中,数据库中的全部数据及数据间的联系都用关系来表示。关系是一个无序的元组集合,每个元组由一组属性值构成,表示一个实体。一个有n个属性的关系称为n元关系。由于关系中的元组是无序的,因此DBMS可以采用任何它希望的方式存储它们,以便进行优化。
#### 1.3.1.2 主键和外键
主键和外键反映了关系模型的实体完整性约束和参照完整性约束。
主键可唯一地标识关系中的一个元组,以确保没有任何两个元组是完全一样的。如果用户没有定义主键,有些DBMS会自动创建内部主键。
外键指定一个关系中的属性在取值时必须与另一个关系中的某个元组相对应,不能随意取值。
#### 1.3.1.3 关系代数
关系代数是关系模型定义的一组运算符,用于检索和操作关系中的元组。每个运算符接受一个或多个关系作为输入,并输出一个新的关系。为了表示查询,可以将这些运算符连接在一起以创建更复杂的运算,称为关系代数表达式。
常见的关系代数运算符包括:
- **选择(selection)**:选择运算是从关系R中选取满足给定条件的元组构成结果关系,记作σF(R)。
- **投影(Projection)** :投影运算是从关系R中选取若干属性列A构成结果关系,记作 ΠA(R)。
- **并( Union )** :两个关系R和S的并是由属于R或属于S的元组构成的集合,记为 R∪S。
- **交( Intersection)** :两个关系R和S的交是由既属于R又属于S的元组构成的集合,记为 R ∩ S。
- **差(Difference )** :两个关系R和S的差是由属于R但不属于S的元组构成的集合,记为 R-S。
- **笛卡尔积( Cartesian Product)** :两个关系R和S的笛卡尔积是由这两个关系中元组拼接而成的所有可能的元组的集合,记为R×S。
- **自然连接(Natural Join)** :两个关系R和S的自然连接是由这两个关系中在共同属性上取值相等的元组拼接而成的所有可能的元组的集合,记为R⋈S。
关系代数可以被视为一种过程化语言,因为一个关系代数表达式指定了查询的具体计算步骤。例如, ![1.3.1.3-1](images/1.3.1.3-1.png) 指定的计算步骤是先计算关系S和SC的自然连接,然后选择,而 ![1.3.1.3-2](images/1.3.1.3-2.png) 指定的计算步骤则是先选择后连接。这两个表达式其实是等价的,它们的计算结果相同,但是计算速度却不同,后者明显更快。如果像这样由用户来指定查询的计算步骤,性能优化的压力就会落在用户身上,因为他们必须考虑如何写出更高效的查询表达式。所以更好的方法是DBMS提供一种非过程化语言,用户只指定需要什么数据,而不指定如何找到它。这正是SQL的成功之处。
### 1.3.2 SQL
SQL 是关系数据库的标准语言,它是1974 年由Boyce和Chamberlin提出的,最初叫 Seque(Structured English Query Language), 并在IBM公司研发的关系数据库管理系统原型System R上实现,后改名为SQL(Structured Query Language)。SQL是一种通用的、功能极强的关系数据库语言,其功能不仅仅是查询,而是包括数据库模式创建、数据库数据的插入与修改、数据库安全性完整性定义与控制等一系列功能。但是,数据查询仍然是SQL中最重要、也最具特色的功能。
关系模型中的关系在SQL中被映射为表或视图。其中,表是指数据实际存储在数据库中的关系,视图是指不实际存储数据,但是需要时可以由实际存储的关系构造出来的关系。
需要指出的是,关系模型中的关系和SQL中的表和视图在概念上存在一些差异。前者是基于集合(set)的,即关系中的元组是不允许重复的;而后者是基于包(bag)的,允许表、视图或结果集中出现重复的元组。
SQL的查询通过SELECT语句来表达,它的基本语法如下:
```sql
SELECT <列名或表达式序列>
FROM <表名或视图名序列>
[WHERE <行条件表达式>]
[GROUP BY <列名序列>
[HAVING <组条件表达式>] ]
[ORDER BY <排序列名>[ASC|DESC] [,...]]
```
以上语法成分中,只有SELECT和FROM子句是必不可少的。此外,SQL还提供了一个强大的特性,允许在WHERE、FROM或HAVING子句中嵌入子查询。子查询也是一个SELECT语句,在上述的WHERE、FROM或HAVING子句中可以使用子查询的返回结果来进行计算,这也是SQL之所以称为&quot;结构化&quot;查询语言的原因。
对于一条典型的查询语句,其结果可以这样计算:
1. 读取FROM子句中基本表及视图的数据,并执行笛卡尔积操作;
2. 选取其中满足WHERE子句中条件表达式的元组;
3. 按GROUP BY子句中指定列的值分组;
4. 提取满足HAVING子句中组条件表达式的那些分组;
5. 按SELECT子句投影出结果关系;
6. 按ORDER BY子句对结果关系进行排序。
以上计算过程可以被看作是对一系列关系代数运算的执行。实际上一个SELECT语句在DBMS中就是被解析为一个关系代数表达式,再由执行引擎来对其进行计算的。但是对于同一条SELECT语句,可能存在多个等价的关系代数表达式。例如,对于以下语句:
```sql
SELECT 姓名
FROM 学生, 选课
WHERE 学生.学号=选课.学号 AND 课号=2 ;
```
存在多个等价的关系代数表达式:
1. ​ Π<sub>姓名</sub><sub>学生.学号=选课.学号 ∧ 课号=2 </sub>(学生×选课))
2. ​ Π<sub>姓名</sub><sub>课号=2</sub> (学生⋈选课))
3. ​ Π<sub>姓名</sub>(学生⋈σ<sub>课号=2</sub> (选课)
这三个表达式的计算代价差异巨大,而DBMS的一个重要任务就是通过查询优化处理找到其中代价最小的那一个。SQL采用的这种非过程化语言形式,既简化了用户的表达,又为DBMS优化查询语句的执行性能提供了巨大的灵活性。
此差异已折叠。
此差异已折叠。
# 第4章 查询处理
## 4.1查询处理概述
![图 4-1 关系数据库查询处理流程](images/4-1.png)
<center>图 4-1 关系数据库查询处理流程</center>
关系数据库管理系统查询处理可以分为4个阶段:查询分析、查询检查、查询优化和查询执行。
1. **查询分析** :对用户提交的查询语句进行扫描、词法分析和语法分析,判断是否符合SQL语法规则,若没有语法错误,就会生成一棵语法树。
2. **查询检查** :对语法树进行查询检查,首先根据数据字典中的模式信息检查语句中的数据对象,如关系名、属性名是否存在和有效;还要根据数据字典中的用户权限和完整性约束信息对用户的存取权限进行检查。若通过检查,则将数据库对象的外部名称转换成内部表示。这个过程实际上是对语法树进行语义解析的过程,最后语法树被解析为一个具有特定语义的关系代数表达式,其表示形式仍然是一棵树,称为查询树。
3. **查询优化** :每个查询都会有多种可供选择的执行策略和操作算法,查询优化就是选择一个能高效执行的查询处理策略。一般将查询优化分为代数优化和物理优化。代数优化指对关系代数表达式进行等价变换,改变代数表达式中操作的次序和组合,使查询执行更高效;物理优化则是指存取路径和底层操作算法的选择,选择依据可以是基于规则、代价、语义的。查询优化之后,形成查询计划。
4. **查询执行** :查询计划由一系列操作符构成,每一个操作符实现计划中的一步。查询执行阶段,系统将按照查询计划逐步执行相应的操作序列,得到最终的查询结果。
## 4.2 选择运算
选择操作的典型实现方法有全表扫描法和索引扫描法。
### 4.2.1 全表扫描法
对查询的基本表顺序扫描,逐一检查每个元组是否满足选择条件,把满足条件的元组作为结果输出。
假设可以使用的内存为M块,全表扫描的算法思想如下:
1. 按物理次序读表T的M块到内存;
2. 检查内存的每个元组t,如果t满足选择条件,则输出t;
3. 如果表T还有其他块未被处理,重复(1)和(2)。
这种方法适合小表,对规模大的表要进行顺序扫描,当选择率(即满足条件的元组数占全表比例)较低时,此算法效率很低。
### 4.2.2 索引扫描法
当选择条件中的属性上有索引(例如B+树索引或Hash索引)时,通过索引先找到满足条件的元组指针,再通过元组指针直接在要查询的表中找到元组。
**[例1 ]** 等值查询:`select * from t1 where col=常量`,并且col上有索引(B+树索引或Hash索引均可) ,则使用索引得到col为该常量元组的指针,通过元组指针在表t1中检索到结果。
**[例2 ]** 范围查询: `select * from t1 where col > 常量`,并且col上有B+树索引,使用B+树索引找到col=常量的索引项,以此为入口点在B+树的顺序集上得到col \&gt; 常量的所有元组指针, 通过这些元组指针到t1表中检索满足条件的元组。
**[例 3 ]** 合取条件查询:`select * from t1 where col1=常量a AND col2 >常量b`,如果 col1和 col1上有组合索引(col1,col2),则利用此组合索引进行查询筛选;否则,如果 col1和 col2上分别有索引,则:
方法一:分别利用各自索引查找到满足部分条件的一组元组指针,求这2组指针的交集,再到t1表中检索得到结果。
方法二:只利用索引查找到满足该部分条件的一组元组指针,通过这些元组指针到t1表中检索,对得到的元组检查另一些选择条件是否满足,把满足条件的元组作为结果输出。
一般情况下,当选择率较低时,基于索引的选择算法要优于全表扫描。但在某些情况下,如选择率较高、或者要查找的元组均匀分散在表中,这时索引扫描法的性能可能还不如全表扫描法,因为还需要考虑扫描索引带来的额外开销。
## 4.3 排序运算
排序是数据库中的一个基本功能,用户通过Order by子句即能达到将指定的结果集排序的目的,而且不仅仅是Order by子句,Group by、Distinct等子句都会隐含使用排序操作。
### 4.3.1 利用索引避免排序
为了优化查询语句的排序性能,最好的情况是避免排序,合理利用索引是一个不错的方法。因为一些索引本身也是有序的,如B+树,如果在需要排序的字段上面建立了合适的索引,那么就可以跳过排序过程,提高查询速度。
例如:假设t1表存在B+树索引key1(key\_part1, key\_part2),则以下查询可以利用索引来避免排序:
```sql
SELECT * FROM t1 ORDER BY key_part1, key_part2;
SELECT * FROM t1 WHERE key_part1 = constant ORDER BY key_part2;
SELECT * FROM t1 WHERE key_part1 > constant ORDER BY key_part1;
SELECT * FROM t1 WHERE key_part1 = constant1 AND key_part2 > constant2 ORDER BY key_part2;
```
如果排序字段不在索引中,或者分别存在于多个索引中,或者排序键的字段顺序与组合索引中的字段顺序不一致,则无法利用索引来避免排序。
### 4.3.2 数据库内部排序方法
对于不能利用索引来避免排序的查询,DBMS必须自己实现排序功能以满足用户需求。实现排序的算法可以是文件排序,也可以是内存排序,具体要由排序缓冲区(sort buffer)的大小和结果集的大小来确定。
数据库内部排序的实现主要涉及3种经典排序算法:快速排序、归并排序和堆排序。对于不能全部放在内存中的关系,需要引入外排序,最常用的就是外部归并排序。外部归并排序分为两个阶段:Phase1 – Sorting,对主存中的数据块进行排序,然后将排序后的数据块写回磁盘;Phase2 – Merging,将已排序的子文件合并成一个较大的文件。
#### 4.3.2.1 常规排序法
一般情况下通用的常规排序方法如下:
(1) 从表t中获取满足WHERE条件的记录;
(2) 对于每条记录,将记录的主键+排序键(id,colp)取出放入sort buffer;
(3) 如果sort buffer可以存放所有满足条件的(id,colp)对,则进行排序;否则sort buffer满后,进行排序并固化到临时文件中。(排序算法采用快速排序);
(4) 若排序中产生了临时文件,需要利用归并排序算法,保证临时文件中记录是有序的;
(5) 循环执行上述过程,直到所有满足条件的记录全部参与排序;
(6) 扫描排好序的(id,colp)对,并利用id去取SELECT需要返回的目标列;
(7) 将获取的结果集返回给用户。
从上述流程来看,是否使用文件排序主要看sort buffer是否能容下需要排序的(id,colp)对。此外一次排序涉及两次I/O:第一次是取(id,colp),第二次是取目标列。由于第一次返回的结果集是按colp排序,因此id是乱序的。通过乱序的id去取目标列时,会产生大量的随机I/O。因此,可以考虑对第二次I/O进行优化,即在取数据之前首先将id排序并放入缓冲区,然后按id顺序去取记录,从而将随机I/O转为顺序I/O。
为了避免第二次I/O,还可以考虑一次性取出(id,colp,目标列),当然这样对缓冲区的需求会更大。
#### 4.3.2.2 堆排序法
堆排序法适用于形如&quot;order by limit m,n&quot;的这类排序问题,即跳过m条数据,提取n条数据。这种情况下,虽然仍然需要所有元组参与排序,但是只需要m+n个元组的sort buffer空间即可,对于m和n很小的场景,基本不会出现因sort buffer不够而需要使用临时文件进行归并排序的问题。对于升序,采用大顶堆,最终堆中的元素组成了最小的n个元素;对于降序,则采用小顶堆,最终堆中的元素组成了最大的n的元素。
## 4.4 连接运算
连接操作是查询处理中最常用最耗时的操作之一。主要有4种实现方法:嵌套循环、排序-合并、索引连接和散列连接。
首先引入2个术语:外关系(outer relation)和内关系(inner relation)。外关系是左侧数据集,内关系是右侧数据集。例如:对于A JOIN B,A为外关系,B为内关系。多数情况下,A JOIN B 的成本跟 B JOIN A 的成本是不同的。假定外关系有n个元组,内关系有m个元组。
### 4.4.1 嵌套循环连接
嵌套循环连接是最简单且通用的连接算法,其执行步骤为:针对外关系的每一行,查看内关系里的所有行来寻找匹配的行。这是一个双重循环,时间复杂度为O(n\*m)。
![图 4-2 嵌套循环连接示意图](images/4-2.png)
<center>图 4-2 嵌套循环连接示意图</center>
在磁盘 I/O 方面, 针对外关系的每一行,内部循环需要从内关系读取m行。这个算法需要从磁盘读取 n+ n\*m 行。但是,如果外关系足够小,我们可以把它先读入内存,那么就只需要读取 n+m 行。按照这个思路,外关系就应该选更小的那个关系,因为它有更大的机会装入内存。
当然,内关系如果可以由索引代替,对磁盘 I/O 将更有利。
当外关系太大无法装入内存时,采用块嵌套循环连接方式,对磁盘 I/O 更加有利。其基本思路是将逐行读取数据,改为以页(块)为单位读取数据。算法如下:
(1) 从磁盘读取外关系的一个数据页到内存;
(2) 从磁盘依次读取内关系的所有数据页到内存,与内存中外关系的数据进行比较,保留匹配的结果;
(3) 从磁盘读取外关系的下一个数据页,并继续执行(2),直至外关系的最后一个页面。
与嵌套循环连接算法相比,块嵌套循环连接算法的时间复杂度没有变化,但降低了磁盘访问开销,变为M+M\*N。其中,M为外关系的页数,N为内关系的页数。
### 4.4.2 索引嵌套循环连接
在嵌套循环连接中,若在内关系的连接属性上有索引,则可以用索引查找替代文件扫描。对于外关系的每一个元组,可以利用索引查找内关系中与该元组满足连接条件的元组。这种连接方法称为索引嵌套循环连接,它可以在已有索引或者为了计算该连接而专门建立临时索引的情况下使用。
索引嵌套循环连接的代价可以如下计算。对于外关系的每一个元组,需要先在内关系的索引上进行查找,再检索相关元组。在最坏的情况下,缓冲区只能容纳外关系的一页和索引的一页。此时,读取外关系需M次I/O操作,这里的M指外关系的数据页数;对于外关系中的每个元组,在内关系上进行索引查找,假设索引查找带来的I/O开销为C,则总的I/O开销为:M+(m×C),其中m为外关系的元组数。
这个代价计算公式表明,如果两个关系上均有索引时, 一般把元组较少的关系作外关系时效果较好。
![图4-3 索引连接示意图](images/4-3.png)
<center>图4-3 索引连接示意图</center>
### 4.4.3 排序-合并连接
排序-合并连接算法常用于等值连接,尤其适合参与连接的表已经排好序的情况。其方法如下:
第一步:如果参与连接的表没有排好序,则根据连接属性排序;
第二步:sorted\_merge:
(1) 初始化两个指针,分别指向两个关系的第一个元组;
(2) 比较两个关系的当前元组(当前元组=指针指向的元组);
(3) 如果匹配,保留匹配的结果,两个指针均后移一个位置;
(4) 如果不匹配,就将指向较小元组的那个指针后移一个位置;
(5) 重复步骤(2)、(3)、(4),直到其中一个关系的指针移动到末尾。
![图4-4 排序-合并连接示意图](images/4-4.png)
<center>图4-4 排序-合并连接示意图</center>
因为两个关系都是已排序的,不需要&quot;回头去找&quot;,所以此方法的时间复杂度为O(n+m)。如果两个关系还需要排序,则还要考虑排序的成本:O(n\*Log(n) + m\*Log(m))。
很多情况下,参与连接的数据集已经排好序了,比如:表内部就是有序的,或者参与连接的是查询中已经排好序的中间结果,那么选用排序-合并算法是比较合适的。
### 4.4.4 散列连接
散列连接算法也是适用于等值连接的算法。
散列连接分成两个阶段:第一步,划分阶段,为较小的关系建立hash表,将连接属性作为hash码;第二步,试探阶段,对另一张表的连接属性用同样的hash函数进行散列,将其与相应桶中匹配的元组连接起来。
本算法要求内存足够大,小表的hash表如果能全部放进内存,则效果较好。
![图 4-5 散列连接示意图](images/4-5.png)
<center>图 4-5 散列连接示意图</center>
在时间复杂度方面需要做些假设来简化问题:
(1) 内关系被划分成 X 个散列桶。散列函数几乎均匀地分布每个关系内数据的散列值,即散列桶大小一致。
(2) 外关系的元素与散列桶内所有元素的匹配,成本是散列桶内元素的数量。
算法的开销包括创建散列表的成本(m) +散列函数的计算开销\*n + (m/X) \* n。如果散列函数创建的散列桶的规模足够小,则算法复杂度为O(m+n)。
### 4.4.5 连接算法的选择
具体情况下,应该选择以上哪种连接算法,有许多因素要考量:
(1) 空闲内存:没有足够的内存就无法使用内存中的散列连接。
(2) 两个数据集的大小。比如,如果一个大表连接一个很小的表,那么嵌套循环连接就比散列连接快,因为后者有创建散列表的高昂成本;如果两个表都非常大,那么嵌套循环连接的CPU成本就很高。
(3) 是否有索引:如果连接属性上有两个B+树索引的话,合并连接会是很好的选择。
(4) 关系是否已经排序:这时候合并连接是最好的选择。
(5) 结果是否需要排序:即使参与连接的是未排序的数据集,也可以考虑使用成本较高的合并连接(带排序的),比如得到排序的结果后,我们还可以将它用于另一个合并联接,或者查询中存在ORDER BY/GROUP BY/DISTINCT等操作符,它们隐式或显式地要求一个排序结果。
(6) 连接的类型:是等值连接?还是内连接?外连接?笛卡尔积?或者自连接?有些连接算法在某些情况下是不适用的。
(7) 数据的分布:如果连接条件的数据是倾斜的,用散列连接不是好的选择,因为散列函数将产生分布极不均匀的散列桶。
(8) 多表连接:连接顺序的选择很重要。
另外,还可能考虑实现方式问题,比如连接操作使用多线程或多进程的代价考量。因此,DBMS需要通过查询优化器来选择恰当的执行计划。
## 4.5 表达式计算
如何计算包含多个运算步骤的关系代数表达式?有两种方法:物化计算和流水线计算。
### 4.5.1 物化计算
物化计算以适当的顺序每次执行一次操作;每次计算的结果被物化到一个临时关系以备后用。其缺点为:需要构造临时关系,而且这些临时关系必须写到磁盘上(除非很小)。
表达式的执行顺序可以依据表达式在查询树中的层次而定,从树的底部开始。
![图4-6 一棵查询树](images/4-6.png)
<center>图4-6 一棵查询树</center>
如图4-6所示,此例中只有一个底层运算:department上的选择运算,底层运算的输入是数据库中的关系department。用前面提到的算法执行树中的运算,并将结果存储在临时关系中。在树的高一层中,使用这个临时关系来进行计算,这时输入的要么是临时关系,要么是一个数据库关系。通过重复这一过程,最终可以计算位于树的根节点的运算,从而得到表达式的最终结果。
由于运算的每个中间结果会被物化用于下一层的运算,此方法称为物化计算。物化计算的代价不仅是那些所涉及的运算代价的总和,还可能包括将中间结果写到磁盘的代价。
### 4.5.2 流水线计算
流水线计算可同时计算多个运算,运算的结果传递给下一个,而不必保存临时关系。这种方法通过减少查询执行中产生的临时文件的数量,来提高查询执行的效率。
如图4-6中,可以将选择、连接操作和投影操作组合起来,放入一条流水线,选择得到一个结果传给连接、连接产生一个结果元组马上传送给投影操作去做处理,避免中间结果的创建,从而直接产生最终结果。
创建一个操作的流水线可以带来的好处是:
(1) 消除读和写临时关系的代价,从而减少查询计算代价。
(2) 流水线产生查询结果,边生成边输出给用户,提高响应时间。
流水线可按两种方式来执行:
方式一:需求驱动方式,在操作树的顶端的将数据往上拉。
方式二:生产者驱动方式,将数据从操作树的底层往上推。
需求驱动的流水线方法比生产者驱动的流水线方法使用更广泛,因为它更容易实现。但流水线技术限制了能实现操作的可用算法。例如,若连接运算的左端输入来自流水线,则不能使用排序-合并连接,但可以用索引连接算法。由于这些限制,并非所有情况下流水线方法的代价都小于物化方法。
此差异已折叠。
此差异已折叠。
# 参考资料
1. 王珊, 萨师煊. 数据库系统概论(第5版). 北京: 高等教育出版社, 2014
2. Hector Garcia-Mlina, Jeffrey D. Ullman, Jennifer Widom. 杨冬青 等译. 数据库系统实现. 北京: 机械工业出版社, 2010
3. [Abraham](http://search.dangdang.com/?key2=Abraham&amp;medium=01&amp;category_path=01.00.00.00.00.00) [Silberschatz](http://search.dangdang.com/?key2=Silberschatz&amp;medium=01&amp;category_path=01.00.00.00.00.00), [Henry](http://search.dangdang.com/?key2=Henry&amp;medium=01&amp;category_path=01.00.00.00.00.00) [F.Korth](http://search.dangdang.com/?key2=F.Korth&amp;medium=01&amp;category_path=01.00.00.00.00.00), S. Sudarshan. 杨冬青 等译. 数据库系统概念(第6版). 北京: 机械工业出版社, 2012
4. 李海翔. 数据库查询优化器的艺术原理解析与SQL性能优化. 北京: 机械工业出版社, 2014
5. [https://15445.courses.cs.cmu.edu/fall2020/schedule.html](https://15445.courses.cs.cmu.edu/fall2020/schedule.html)
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册