15. SQL
15. SQL
在计算机中,数据库(Database) 是一种持久化保存数据的方式,通常分为两类: SQL 数据库 和 NoSQL 数据库。SQL 是 Structured Query Language(结构化查询语言)的简称,它是关系型数据库管理系统(RDBMS)的核心组件,用于高效地查询、插入、更新和管理数据库中的数据。
RDBMS(如 MySQL、PostgreSQL、SQL Server)通过结构化的方式组织数据,主要依赖 SQL 语言来操作数据。其底层数据结构常使用 B+ Tree 进行存储。
B+ Tree
B+ Tree 是一种平衡树,用于高效地存储和检索有序数据。B+ Tree 提供快速的搜索、插入、删除操作,并能够很好地支持范围查询和顺序访问,因为其多路平衡特性和磁盘读取优化能力,它被广泛用于数据库索引。
B+ Tree 的每个节点包含键值及指向子节点的指针,叶子节点按顺序存储数据并相互链接。
B+ Tree 的结构特点
多路平衡性:
- B+ Tree 是一种多路搜索树,每个节点可以有多个子节点。
- 非叶子节点存储索引(key),而不存储实际数据。
- 数据仅存储在叶子节点中。
叶子节点链表:
- 所有的叶子节点通过双向链表相连,形成一个有序的链表结构。
- 这使得范围查询、顺序遍历非常高效。
节点分裂和合并:
- 当节点的存储超出容量时,会触发分裂。
- 如果数据量减少导致节点利用率过低,会触发合并操作。
- 这种机制保证了树的平衡,使得树的高度保持最小。
自平衡特性:
- B+ Tree 保持平衡,即从根节点到任意叶子节点的路径长度相等。
- 这种平衡性使得所有操作的时间复杂度为
O(log n)
。
B+ Tree 的优势
- 快速检索:保证操作时间复杂度为
O(log n)
。 - 顺序存取:通过链表支持高效的区间查询。
- 高 IO 效率:设计与磁盘读取特性匹配,减少随机读取。
B+ Tree 示例
假设我们有以下一组数字需要存储在一个 B+ Tree 中:10, 20, 30, 40, 50, 60, 70, 80
- 每个节点最多可存储 3 个键,超出容量时需要分裂。
- 数据存储在叶子节点中,非叶子节点只存储索引。
构建过程如下:
插入 10, 20, 30:
- 根节点能容纳 3 个键,无需分裂。
插入 40:
- 超出容量,节点分裂成两个:左节点存储
[10, 20]
,右节点存储[30, 40]
。 - 父节点新增索引 30。
- 超出容量,节点分裂成两个:左节点存储
插入 50, 60:
- 右节点容量满,再次分裂。左叶节点
[10, 20]
,中叶节点[30, 40]
,右叶节点[50, 60]
。 - 父节点更新为
[30, 50]
。
- 右节点容量满,再次分裂。左叶节点
插入 70, 80:
- 插入数据到右叶节点,最终结构完成。
最终的 B+ Tree 如下:
[30, 50]
/ | \
[10, 20]->[30, 40]->[50, 60, 70, 80]
- 根节点为
[30, 50]
:30 是左子树与中子树的分界点,50 是中子树与右子树的分界点。 - 叶子节点存储所有数据并通过链表链接。
此时,如果要查询 35 到 75 的值,过程如下:
- 先寻找起点 35,从根节点找到范围属于 [30, 50] 的子树,即中子树 [30, 40]。
- 依次遍历链表,找到叶子节点 [30, 40] 和 [50, 60, 70, 80]。
- 过滤出属于 35 到 75 范围内的值,返回值:[40, 50, 60, 70]。
这种设计有效利用了磁盘页的读取特性,减少随机 IO 操作,提升性能。
SQL
使用示例
以电话本为例,数据库中的一个表可以存储如下信息:
ID | Name | Phone |
---|---|---|
1 | Alice | 123-456-7890 |
2 | Bob | 234-567-8901 |
3 | Charlie | 345-678-9012 |
查询示例
- 查询所有联系人:
SELECT * FROM PhoneBook;
- 查询名字为 Alice 的电话:
SELECT Phone FROM PhoneBook WHERE Name = 'Alice';
- 添加新联系人:
INSERT INTO PhoneBook (Name, Phone) VALUES ('David', '456-789-0123');
两个表之间的查询示例
以一个简单的学生和课程管理系统为例,有两个表:students
和 courses
。它们通过外键关系连接,展示 一对多 的关系。
表结构
students
表 (学生信息表):id
: 学生的唯一 IDname
: 学生姓名
courses
表 (课程信息表):id
: 课程的唯一 IDstudent_id
: 学生的 ID(外键,指向students
表的id
列)course_name
: 学生选择的课程名称
表数据示例:
students
表:
id | name |
---|---|
1 | Alice |
2 | Bob |
3 | Charlie |
courses
表:
id | student_id | course_name |
---|---|---|
1 | 1 | Math |
2 | 1 | Science |
3 | 2 | History |
4 | 3 | Math |
5 | 3 | Physics |
查询示例
- 查询某个学生及其所选课程
例如,查询Alice
的课程:
SELECT students.name, courses.course_name
FROM students
JOIN courses ON students.id = courses.student_id
WHERE students.name = 'Alice';
结果:
name | course_name |
---|---|
Alice | Math |
Alice | Science |
- 查询所有学生及其课程
SELECT students.name, courses.course_name
FROM students
JOIN courses ON students.id = courses.student_id;
结果:
name | course_name |
---|---|
Alice | Math |
Alice | Science |
Bob | History |
Charlie | Math |
Charlie | Physics |
通过建立两个表的关系,可以高效查询一对多的关系数据,甚至扩展到更复杂的场景,比如多对多或嵌套查询。这种设计使得关系型数据库能够灵活应对不同的数据管理需求。
SQL 的核心特性:ACID
为了确保数据库操作的可靠性,SQL 数据库遵循 ACID 属性:
1. Durability(持久性)
Durability 确保当事务完成后,数据会永久保存在存储设备上,即使系统发生崩溃。
Redis 通常不完全符合 Durability,因为它是内存数据库,某些配置下数据可能未写入磁盘。
事务的典型操作流程:
- 开始事务(
BEGIN
)。 - 执行操作(
READ
、WRITE
、UPDATE
、DELETE
)。 - 提交事务(
COMMIT
)。
2. Atomicity(原子性)
原子性确保事务中的所有操作要么全部执行,要么完全回滚。
示例:银行转账
- A 给 B 转账 500 元时,如果 A 转出成功但 B 账户更新时系统崩溃,A 的 500 元不会消失,而是整个事务回滚:
转账前:A: 1000,B: 500 转账后(崩溃):A: 1000,B: 500
3. Isolation(隔离性)
隔离性确保多个事务同时执行时,其结果与按顺序执行的结果一致。
示例:多事务并发
- A 给 B 转 500 元,C 给 A 转 200 元。在 A 执行转账后,C 的操作不能影响事务的中间状态。
4. Consistency(一致性)
一致性确保事务前后,数据库都处于合法状态。
示例:
- 如果数据库的规则要求账户余额总和不变,任何事务完成后都必须满足此规则。