数据库
数据库是一个有组织的、相互关联的数据集合,用来模拟现实世界中的某些方面(例如,模拟班级中的学生或数字音乐商店)。人们常常将“数据库”和“数据库管理系统”(例如 MySQL、Oracle、MongoDB)混为一谈。数据库管理系统(DBMS)是用于管理数据库的软件。
考虑一个模拟数字音乐商店(例如 Spotify)的数据库。假设这个数据库存储了关于艺术家及其发行的专辑的信息。
平面文件模型
数据库可以存储为逗号分隔值(CSV)文件,并由数据库管理系统管理。每个实体会存储在独立的文件中。当应用程序需要读取或更新记录时,必须每次都解析这些文件。每个实体有自己的一组属性,因此在每个文件中,不同的记录通过换行符分隔,而每个记录的属性则通过逗号分隔。
继续以数字音乐商店为例,可能有两个文件:一个存储艺术家信息,另一个存储专辑信息。艺术家可能有姓名、出生年份和国家等属性,而专辑则有名称、艺术家和年份等属性。
平面文件的问题
- 数据完整性:
- 如何确保每张专辑的艺术家信息一致?
- 如果有人将专辑年份覆盖为无效的字符串怎么办?
- 如何存储一张专辑中有多个艺术家的情况?
- 实现问题:
- 如何找到特定的记录?
- 如果我们现在想要创建一个新的应用程序使用同一个数据库怎么办?
- 如果两个线程同时试图写入同一个文件怎么办?
- 持久性问题:
- 如果程序在更新记录时机器崩溃怎么办?
- 如果我们希望在多台机器上复制数据库以提高可用性怎么办?
数据库管理系统
数据库管理系统(DBMS)是一种软件,允许应用程序在数据库中存储和分析信息。
通用数据库管理系统的设计目的是允许定义、创建、查询、更新和管理数据库。
早期的数据库管理系统 早期的数据库应用程序很难构建和维护,因为逻辑层和物理层之间紧密耦合。逻辑层定义了数据库中的实体和属性,而物理层则定义了这些实体和属性的存储方式。早期的物理层是在应用程序代码中定义的,因此如果我们想要改变应用程序使用的物理层,就必须修改所有代码以适应新的物理层。
关系模型
Ted Codd 观察到每次更改物理层时,都会重写数据库管理系统。为了解决这一问题,他在1970年提出了关系模型。关系模型有三个关键点:
- 使用简单的数据结构(关系)存储数据库。
- 通过高级语言访问数据。
- 物理存储由实现决定。
数据模型 数据模型是用于描述数据库中数据的概念集合。关系模型就是一种数据模型。
模式(Schema) 模式是使用给定数据模型对特定数据集合的描述。
关系数据模型的三大概念:
- 结构:定义关系及其内容。这包括关系的属性及其属性可以包含的值。
- 完整性:确保数据库的内容满足一定的约束。例如,年份属性的值必须是数字。
- 操作:如何访问和修改数据库内容。
关系(Relation) 关系是一个无序集合,包含表示实体的属性关系。由于关系是无序的,数据库管理系统可以以任何它想要的方式存储它们,从而允许进行优化。
元组(Tuple)
元组是关系中属性值的集合(也称为域)。最初,值必须是原子(atomic)或标量(scalar),但现在值可以是列表或嵌套的数据结构。每个属性可以有一个特殊值
NULL
,表示对于给定元组,属性是未定义的。
一个有 n 个属性的关系被称为 n 元关系(n-ary relation)。
键(Keys) 关系的主键(Primary Key)唯一标识一个元组。如果你不定义主键,一些数据库管理系统会自动创建一个内部主键。许多数据库管理系统支持自动生成键,因此应用程序不必手动增加键。
外键(Foreign Key)指定一个关系中的属性必须映射到另一个关系中的元组。
数据操作语言(DMLs)
数据操作语言用于存储和检索数据库中的信息。DMLs 分为两类:
- 过程式(Procedural):查询指定数据库管理系统应该如何找到所需的结果(高层策略)。
- 非过程式(Non-Procedural):查询只指定所需的数据,而不指定如何查找它。
关系代数(Relational Algebra)
关系代数是一组用于检索和操作关系中元组的基本操作。每个操作符接受一个或多个关系作为输入,并输出一个新的关系。我们可以将这些操作符“链接”起来,创建更复杂的操作。
选择(Selection) 选择操作符从关系中输出满足条件的元组子集。谓词(Predicate)充当过滤器,我们可以使用逻辑运算符(如与、或)结合多个谓词。
语法:
σ_{谓词}(R)
投影(Projection) 投影操作符从关系中输出只包含指定属性的元组。你可以重新排列输入关系中属性的顺序,并且可以操作这些值。
语法:
π_{A1,A2,...,An}(R)
并集(Union) 并集操作符接受两个关系,输出至少出现在一个输入关系中的所有元组。注意:两个输入关系必须具有完全相同的属性。
语法:
(R ∪ S)
交集(Intersection) 交集操作符接受两个关系,输出同时出现在两个输入关系中的所有元组。注意:两个输入关系必须具有完全相同的属性。
语法:
(R ∩ S)
差集(Difference) 差集操作符接受两个关系,输出出现在第一个关系中但不在第二个关系中的元组。注意:两个输入关系必须具有完全相同的属性。
语法:
(R - S)
乘积(Product) 乘积操作符接受两个关系,输出输入关系中元组的所有可能组合。
语法:
(R × S)
连接(Join) 连接操作符接受两个关系,输出元组组合。对于每个共享的属性,元组中的值必须相同。
语法:
(R ⨝ S)
关系代数操作示例
数据库示例:
假设我们有以下两个关系:
学生表 (Students):
ID
:学生的唯一标识符Name
:学生的名字Age
:学生的年龄
1
2
3
4
5
6
7
8
9Students
+----+--------+-----+
| ID | Name | Age |
+----+--------+-----+
| 1 | Alice | 20 |
| 2 | Bob | 22 |
| 3 | Charlie| 23 |
+----+--------+-----+课程表 (Courses):
CourseID
:课程的唯一标识符CourseName
:课程名称StudentID
:学生的ID(外键,指向学生表的ID
)
1
2
3
4
5
6
7
8
9
10
11Courses
+---------+------------+-----------+
| CourseID| CourseName | StudentID |
+---------+------------+-----------+
| 101 | Math | 1 |
| 102 | English | 2 |
| 103 | History | 3 |
| 104 | Math | 2 |
+---------+------------+-----------+选择 (Selection)
选择操作从关系中挑选满足特定条件的元组(记录)。例如,我们希望选择所有年龄大于 21 岁的学生。
操作:
1 | σ_{Age > 21} (Students) |
结果:
1 | plaintext复制代码+----+--------+-----+ |
- 投影 (Projection)
投影操作用于选择特定的列。例如,我们只想查看学生的名字和年龄。
操作:
1 | π_{Name, Age} (Students) |
结果:
1 | +--------+-----+ |
- 并集 (Union)
并集操作用于合并两个关系,输出所有元组。假设我们有两个不同的学生表,分别表示参加数学和英语课程的学生,我们希望找到参加任一课程的学生。
操作:
1 | MathStudents ∪ EnglishStudents |
假设两个关系如下:
1 | MathStudents |
结果:
1 | +----+--------+ |
- 交集 (Intersection)
交集操作用于查找同时出现在两个关系中的元组。例如,我们希望找到既修数学又修英语课程的学生。
操作:
1 | MathStudents ∩ EnglishStudents |
结果:
1 | +----+--------+ |
- 差集 (Difference)
差集操作返回出现在第一个关系中,但不在第二个关系中的元组。例如,我们希望找到只修数学课程但不修英语课程的学生。
操作:
1 | MathStudents - EnglishStudents |
结果:
1 | +----+--------+ |
- 笛卡尔积 (Product)
笛卡尔积用于生成两个关系的所有可能组合。例如,我们希望将每个学生和每门课程进行组合。
操作:
1 | Students × Courses |
结果: (仅显示部分结果)
1 | +----+--------+-----+---------+------------+-----------+ |
- 连接 (Join)
连接操作用于将两个关系组合在一起,其中共享属性的值必须相同。例如,我们希望找到每个学生修的课程信息。
操作:
1 | Students ⨝ Courses |
结果:
1 | +----+--------+-----+---------+------------+ |