初识数据库

数据库

数据库是一个有组织的、相互关联的数据集合,用来模拟现实世界中的某些方面(例如,模拟班级中的学生或数字音乐商店)。人们常常将“数据库”和“数据库管理系统”(例如 MySQL、Oracle、MongoDB)混为一谈。数据库管理系统(DBMS)是用于管理数据库的软件。

考虑一个模拟数字音乐商店(例如 Spotify)的数据库。假设这个数据库存储了关于艺术家及其发行的专辑的信息。

平面文件模型

数据库可以存储为逗号分隔值(CSV)文件,并由数据库管理系统管理。每个实体会存储在独立的文件中。当应用程序需要读取或更新记录时,必须每次都解析这些文件。每个实体有自己的一组属性,因此在每个文件中,不同的记录通过换行符分隔,而每个记录的属性则通过逗号分隔。

继续以数字音乐商店为例,可能有两个文件:一个存储艺术家信息,另一个存储专辑信息。艺术家可能有姓名、出生年份和国家等属性,而专辑则有名称、艺术家和年份等属性。

平面文件的问题

  • 数据完整性
    • 如何确保每张专辑的艺术家信息一致?
    • 如果有人将专辑年份覆盖为无效的字符串怎么办?
    • 如何存储一张专辑中有多个艺术家的情况?
  • 实现问题
    • 如何找到特定的记录?
    • 如果我们现在想要创建一个新的应用程序使用同一个数据库怎么办?
    • 如果两个线程同时试图写入同一个文件怎么办?
  • 持久性问题
    • 如果程序在更新记录时机器崩溃怎么办?
    • 如果我们希望在多台机器上复制数据库以提高可用性怎么办?

数据库管理系统

数据库管理系统(DBMS)是一种软件,允许应用程序在数据库中存储和分析信息。

通用数据库管理系统的设计目的是允许定义、创建、查询、更新和管理数据库。

早期的数据库管理系统 早期的数据库应用程序很难构建和维护,因为逻辑层和物理层之间紧密耦合。逻辑层定义了数据库中的实体和属性,而物理层则定义了这些实体和属性的存储方式。早期的物理层是在应用程序代码中定义的,因此如果我们想要改变应用程序使用的物理层,就必须修改所有代码以适应新的物理层。

关系模型

Ted Codd 观察到每次更改物理层时,都会重写数据库管理系统。为了解决这一问题,他在1970年提出了关系模型。关系模型有三个关键点:

  1. 使用简单的数据结构(关系)存储数据库。
  2. 通过高级语言访问数据。
  3. 物理存储由实现决定。

数据模型 数据模型是用于描述数据库中数据的概念集合。关系模型就是一种数据模型。

模式(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)

关系代数操作示例

数据库示例:

假设我们有以下两个关系:

  1. 学生表 (Students)

    • ID:学生的唯一标识符
    • Name:学生的名字
    • Age:学生的年龄
    1
    2
    3
    4
    5
    6
    7
    8
    9
    Students
    +----+--------+-----+
    | ID | Name | Age |
    +----+--------+-----+
    | 1 | Alice | 20 |
    | 2 | Bob | 22 |
    | 3 | Charlie| 23 |
    +----+--------+-----+

  2. 课程表 (Courses)

    • CourseID:课程的唯一标识符
    • CourseName:课程名称
    • StudentID:学生的ID(外键,指向学生表的 ID
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Courses
    +---------+------------+-----------+
    | CourseID| CourseName | StudentID |
    +---------+------------+-----------+
    | 101 | Math | 1 |
    | 102 | English | 2 |
    | 103 | History | 3 |
    | 104 | Math | 2 |
    +---------+------------+-----------+


  3. 选择 (Selection)

选择操作从关系中挑选满足特定条件的元组(记录)。例如,我们希望选择所有年龄大于 21 岁的学生。

操作

1
σ_{Age > 21} (Students)

结果

1
2
3
4
5
6
plaintext复制代码+----+--------+-----+
| ID | Name | Age |
+----+--------+-----+
| 2 | Bob | 22 |
| 3 | Charlie| 23 |
+----+--------+-----+
  1. 投影 (Projection)

投影操作用于选择特定的列。例如,我们只想查看学生的名字和年龄。

操作

1
π_{Name, Age} (Students)

结果

1
2
3
4
5
6
7
+--------+-----+
| Name | Age |
+--------+-----+
| Alice | 20 |
| Bob | 22 |
| Charlie| 23 |
+--------+-----+
  1. 并集 (Union)

并集操作用于合并两个关系,输出所有元组。假设我们有两个不同的学生表,分别表示参加数学和英语课程的学生,我们希望找到参加任一课程的学生。

操作

1
MathStudents ∪ EnglishStudents

假设两个关系如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
MathStudents
+----+--------+
| ID | Name |
+----+--------+
| 1 | Alice |
| 2 | Bob |
+----+--------+

EnglishStudents
+----+--------+
| ID | Name |
+----+--------+
| 2 | Bob |
| 3 | Charlie|
+----+--------+

结果

1
2
3
4
5
6
7
+----+--------+
| ID | Name |
+----+--------+
| 1 | Alice |
| 2 | Bob |
| 3 | Charlie|
+----+--------+
  1. 交集 (Intersection)

交集操作用于查找同时出现在两个关系中的元组。例如,我们希望找到既修数学又修英语课程的学生。

操作

1
MathStudents ∩ EnglishStudents

结果

1
2
3
4
5
+----+--------+
| ID | Name |
+----+--------+
| 2 | Bob |
+----+--------+
  1. 差集 (Difference)

差集操作返回出现在第一个关系中,但不在第二个关系中的元组。例如,我们希望找到只修数学课程但不修英语课程的学生。

操作

1
MathStudents - EnglishStudents

结果

1
2
3
4
5
+----+--------+
| ID | Name |
+----+--------+
| 1 | Alice |
+----+--------+
  1. 笛卡尔积 (Product)

笛卡尔积用于生成两个关系的所有可能组合。例如,我们希望将每个学生和每门课程进行组合。

操作

1
Students × Courses

结果: (仅显示部分结果)

1
2
3
4
5
6
7
8
9
10
+----+--------+-----+---------+------------+-----------+
| ID | Name | Age | CourseID| CourseName | StudentID |
+----+--------+-----+---------+------------+-----------+
| 1 | Alice | 20 | 101 | Math | 1 |
| 1 | Alice | 20 | 102 | English | 2 |
| 1 | Alice | 20 | 103 | History | 3 |
| 1 | Alice | 20 | 104 | Math | 2 |
| 2 | Bob | 22 | 101 | Math | 1 |
| ...(省略其余组合)
+----+--------+-----+---------+------------+-----------+
  1. 连接 (Join)

连接操作用于将两个关系组合在一起,其中共享属性的值必须相同。例如,我们希望找到每个学生修的课程信息。

操作

1
Students ⨝ Courses

结果

1
2
3
4
5
6
7
8
+----+--------+-----+---------+------------+
| ID | Name | Age | CourseID| CourseName |
+----+--------+-----+---------+------------+
| 1 | Alice | 20 | 101 | Math |
| 2 | Bob | 22 | 102 | English |
| 2 | Bob | 22 | 104 | Math |
| 3 | Charlie| 23 | 103 | History |
+----+--------+-----+---------+------------+

欢迎关注我的其它发布渠道