软考高级架构师-数据库系统(重点)
编写于 2025.08.13数据库系统题型为选择题、案例题、论文题,分值在4-9分。
数据库模式

数据库三级模式结构:
- 外模式:它是数据库的用户视图或子模式。它是从概念模式派生出来的,代表了特定应用程序或特定用户组所看到和使用的数据库局部逻辑结构和数据。 当概念模式发生变化(如增加一个新字段、修改表结构),只要这些变化不影响某个外模式所需的数据,那么基于该外模式的应用程序无需修改就能继续工作。DBA只需要修改外模式到概念模式的映射即可。
- 概念模式:它是数据库的全局逻辑视图。它描述了整个数据库中所有数据的逻辑结构和约束(实体、属性、关系、数据类型、完整性约束、安全性约束等)。 就像一栋大楼的完整建筑设计蓝图,展示了所有房间、走廊、管道、电路的结构和连接,但不指定具体用什么品牌的砖头或电线(物理细节),也不指定某个租户看到的办公室布局(用户视图)。
- 内模式:它是数据库的物理存储视图。它描述了数据在计算机存储设备(如磁盘)上如何实际存储的细节。 当内模式发生变化(如更换存储设备、优化索引结构、重组文件物理位置),只要这些变化不改变概念模式(即数据的逻辑结构),那么概念模式和外模式都无需修改,应用程序也感知不到变化(可能性能会变好或变差)。
关系的3种类型,反映了数据在不同场景下的存在形式:
- 基本关系(通常又称为基本表或基表):实际存在的表,实际存储数据的逻辑表示。
- 查询表:查询结果对应的表。
- 视图表:由基表或其他视图表导出的表,本身不独立存储,数据库只存放它的定义,常称为虚表。
视图类型:
- 数据库视图:它一个虚拟表(逻辑上的表),其内容由查询定义(仅保存SQL查询语句)。 同真实的表一样,视图包含一系列带有名称的列和行数据。但是,视图并没有真正存储这些数据,而是通过查询原始表动态生成所需要的数据。
- 物化视图:它不是传统意义上虚拟视图,是实体化视图,其本身会存储数据。同时当原始表中的数据更新时,物化视图也会更新。
视图的优点:
- 视图能简化用户操作
- 视图使用户能以多种角度看待同一数据
- 视图对重构数据库提供了一定程度的逻辑独立性
- 视图可以对机密数据提供安全保护
分布式数据库
分布式数据库是相对应集中式数据库而言,分布式数据库是将数据库分布到多个数据库服务器上,每个数据库服务器负责处理一部分数据。
分布式数据库特点:
- 数据独立性。除了数据的逻辑独立性与物理独立性外,还有数据分布独立性(分布透明性)。
- 集中与自治共享结合的控制结构。各局部的DBMS可以独立地管理局部数据库,具有自治的功能。 同时,系统又设有集中控制机制,协调各局部DBMS的工作,执行全局应用。
- 适当增加数据冗余度。在不同的场地存储同一数据的多个副本,可以提高系统的可靠性和可用性,同时也能提高系统性能。 (提高系统的可用性,即当系统中某个节点发生故障时,因为数据有其他副本在非故障场地上,对其他所有场地来说,数据仍然是可用的,从而保证数据的完备性。
- 全局的一致性、可串行性和可恢复性。
分布式数据库模式:

- 全局外模式:用户或应用程序视角的数据视图,屏蔽底层分布式细节。提供定制化的数据逻辑视图(如部分表、字段),用户无需知晓数据分片、存储位置或复制机制。
比如,用户查询
SELECT * FROM orders,无需知道orders表实际被分片存储在多个节点。 - 全局概念模式:整个分布式数据库的逻辑结构,描述所有数据的实体、关系、约束。整个分布式数据库的逻辑结构,描述所有数据的实体、关系、约束,提供数据一致性的逻辑约束(如全局唯一约束)。
- 分片模式:描述如何将全局数据拆分为分片。
- 水平分片:按行拆分(如 user_id < 1000 的订单存到节点A)。
- 垂直分片:按列拆分(如订单的 id, amount 存节点A,address, phone 存节点B)。
- 混合分片:组合上述两种方式。
- 分布模式:描述分片在物理节点上的位置和副本管理。
- 局部概念模式:单个节点上分片的逻辑结构。描述该节点存储的分片对应的表结构、约束,与全局概念模式对齐,但仅包含局部数据。
- 局部内模式:单个节点上数据的物理存储细节。比如,orders_shard1 在 Node1 使用 B+树索引存储为 /data/orders_shard1.ibd。
分布式数据库管理系统:
- 组成:局部数据库管理系统(LDBMS)、全局数据库管理系统(GDBMS)、全局数据字典、通信管理(CM)
- 结构:全局控制集中的分布式数据库管理系统、全局控制分散的分布式数据库管理系统、全局控制部分分散的分布式数据库管理系统(分布式数据库管理系统-DDBMS)
分片透明性:
- 分片透明:是指用户不必关心数据是如何分片的,它们对数据的操作在全局关系上进行,即如何分片对用户是透明的。
- 复制透明:用户不用关心数据库在网络中各个节点的复制情况,被复制的数据的更新都由系统自动完成。
- 位置透明:是指用户不必知道所操作的数据放在何处,即数据分配到哪个或哪些站点存储对用户是透明的。
- 局部映像透明性(逻辑透明):是最低层次的透明性,该透明性提供数据到局部数据库的映像,即用户不必关心局部DBMS支持哪种数据模型、使用哪种数据操纵语言,数据模型和操纵语言的转换是由系统完成的。 因此,局部映像透明性对异构型和同构异质的分布式数据库系统是非常重要的。
分布式事务,两阶段提交协议2PC:
- 2PC事务提交的两个阶段
- 表决阶段,目的是形成一个共同的决定
- 执行阶段,目的是实现这个协调者的决定
- 两条全局提交规则
- 只要有一个参与者撤销事务,协调者就必须做出全局撤销决定
- 只有所有参与者都同意提交事务,协调者才能做出全局提交决定
数据库设计过程

- 需求分析:明确系统要存储哪些数据及操作需求。输出:需求说明书(包含数据字典、业务规则、数据流图)。
- 概念结构设计:创建独立于技术实现的高层数据模型。输出:ER模型。
- 逻辑结构设计:将概念模型转化为数据库可识别的结构(如表、键)。输出:关系模式图(含所有表结构)。
- 物理设计:针对具体数据库系统(MySQL、Oracle等)优化存储和性能。输出:SQL DDL脚本(建表语句、索引、分区方案)。
概念结构设计-ER模型
概念结构设计的目标是准确反映现实世界的信息需求,捕获用户关心的所有数据对象及其联系。使用图形化表示(ER图),让用户、设计人员、开发者等不同角色都能清晰沟通。

它的产物是ER模型,E是实体(Entity)客观存在并可相互区分的事物或概念,它代表现实世界中一类独立的对象; R是联系(Relationship)是实体集之间或实体集内部的相互关联,在ER图中通常用菱形表示,并用无向线将菱形与相关联的实体集连接起来。菱形框内写上联系名。

映射基数/联系基数(Mapping Cardinalities): 定义了一个实体通过联系能关联到的另一个实体的数量。这是描述联系本质的关键。

两个不同实体集之间的联系:
- 一对一(1:1)
- 一对多(1:n)
- 多对多(n:n)
当系统由多个子系统组成或由不同团队/用户组分别设计局部ER图时,就需要合并ER图(也称为视图集成或综合)。集成的方法:
- 多个局部E-R图一次集成。
- 逐步集成,用累加的方式一次集成两个局部E-R。
集成产生的冲突及解决办法:
- 属性冲突:包括属性域冲突和属性取值冲突
- 命名冲突:包括同名异义和异名同义。
- 结构冲突:包括同一对象在不同应用中具有不同的抽象,以及同一实体在不同局部E-R图中所包含的属性个数和属性排列次序不完全相同。
逻辑结构设计-关系模式
关系模型与逻辑结构设计是数据库设计流程中的核心环节,它承接着概念结构设计(ER模型),并为物理结构设计奠定基础。

相关概念:
- 主键(Primary Key, PK): 唯一标识表中每条记录的列或列组合(非空且唯一)。例如:学生表的主键是学号。
- 外键(Foreign Key, FK): 一个表中的列,引用另一个表的主键,用于强制表间关系和参照完整性。例如:选课表中的学号是外键,引用学生表的学号。
- 候选键(Candidate Key): 能作为主键的列或列组合(满足唯一性和最小性)。
完整性约束:
- 实体完整性:规定基本关系的主属性,不能取空值。
- 参照完整性:外键值必须是被引用表的主键值或NULL。
- 用户定义完整性: 业务规则(如年龄 > 0,性别 IN (‘男’,‘女’))。
从ER图到关系模式,这是逻辑设计的核心任务,需遵循系统化的转换规则将ER模型映射为关系表。 一个实体型必须转换为一个关系模式。
联系转关系模式:

- 一对一联系的转换有两种方式。
- 独立的关系模式:并入两端主键及联系自身属性。(主键:任一端主键)
- 归并(任意一端):并入另一端主键及联系自身属性。(主键:保持不变)
- 一对多联系的转换有两种方式。
- 独立的关系模式:并入两端主键及联系自身属性。(主键:多端主键)
- 归并(多端):并入另一端主键及联系自身属性。(主键:保持不变)
- 多对多联系的转换只有一种方式。
- 独立的关系模式:并入两端主键及联系自身属性。(主键:两端主键的组合键)
关系代数
关系代数是关系型数据库的理论基础。它是一种过程性查询语言,提供了一组形式化的操作符,用于对关系(即数据库表) 进行操作,最终产生一个新的关系作为结果。
核心的关系代数操作符:



- 选择 (Selection - σ):从一个关系中选择满足指定条件的元组(行)。
- 投影 (Projection - π):从一个关系中选择指定的属性(列),并去除重复元组(如果需要)。
- 并 (Union - ∪):将两个具有相同模式的关系中的所有元组合并起来,并自动去除重复元组。
- 差 (Set Difference - -):找出属于第一个关系但不属于第二个关系的元组。两个关系必须具有相同模式。
- 笛卡尔积 (Cartesian Product - ×):将两个关系的所有元组进行组合。如果 R 有 m 个元组,S 有 n 个元组,则 R × S 有 m * n 个元组。
- 自然连接 (Natural Join - ⋈):根据两个关系中具有相同名称的属性进行等值连接,并自动去除重复的属性。
规范化理论(重点、难点)
非规范化的关系模式,可能存在的问题包括:数据冗余、更新异常(修改操作一致性问题)、插入异常、删除异常。
- 数据冗余:相同的信息在多个元组中重复存储,浪费存储空间。
- 更新异常:修改数据时,可能需要修改多处冗余副本。如果只修改了部分副本而没有全部修改,就会导致数据不一致。
- 插入异常:因为缺少主键的一部分(或其他强制依赖),无法插入某些合法或必需的信息。
- 删除异常:删除某些信息时,可能会意外地丢失其他本应保留的信息。
函数依赖
函数依赖描述了关系中属性集之间的一种约束关系。它是现实世界数据语义的体现。
在一个关系模式 R(U) 中,X 和 Y 是属性集 U 的子集。如果对于 R 的任意两个元组 t1 和 t2,只要 t1[X] = t2[X],就必然有 t1[Y] = t2[Y],则称 Y 函数依赖于 X,或者说 X 函数确定 Y,记作 X → Y。
- X 称为决定因素
- Y 称为被决定因素
假设有一个 学生选课 关系,包含属性:学号(Sno), 姓名(Sname), 课程号(Cno), 成绩(Grade), 学分(Credit)。 学号 → 姓名 语义:一个学号唯一对应一个学生姓名。只要学号相同,姓名必定相同。
-
部分函数依赖 :如果 X → Y,但 Y 不完全依赖于 X,即存在 X 的真子集 X‘,使得 X’ → Y。记作 X →ᴾ Y。 例子:在 (Sno, Cno, Sname) 中,(Sno, Cno) → Sname 是一个部分函数依赖,因为实际上 Sno → Sname 已经成立。Sname 只部分依赖于主键(即只依赖于 Sno)。

-
传递函数依赖 :如果 X → Y, Y → Z,且 Y ↛ X,则称 Z 传递函数依赖于 X。记作 X →ᵀ Z。 例子:在 (Sno, Sdept, Mname) 中,Sno → Sdept, Sdept → Mname(一个系只有一位系主任),且 Sdept ↛ Sno,所以 Sno →ᵀ Mname。

候选键
候选键是能够唯一标识关系中每个元组(一行数据)的最小属性集合。
求候选键步骤:
- 将关系模式的函数依赖关系用“有向图”的方式表示
- 找入度为0的属性,并以该属性集合为起点,尝试遍历有向图,若能正常遍历图中所有结点,则该属性集即为关系模式的候选键
- 若入度为0的属性集不能遍历图中所有结点,则需要尝试性的将一些中间结点(既有入度,也有出度的结点)并入入度为0的属性集中,直至该集合能遍历所有结点,集合为候选键
- 入度:指指向该节点的边的数量,即有多少个属性函数确定该属性。入度为0的属性表示没有其他属性函数确定它,因此它必须是候选键的一部分(因为它不能被其他属性推导出)。
- 出度:指从该节点出发的边的数量,即该属性函数确定多少个其他属性。出度为0的属性表示它不函数确定任何其他属性(除了可能自身),通常是非主属性。
例题:给定关系R(A1,A2,A3,A4)上的函数依赖集F={A1→A2,A3→A2,A2→A3,A2→A4},R的候选关键字为 A1 。解题思路:
- 构建函数依赖有向图:

- 计算节点入度:
- A1:入度为 0(没有边指向它)
- A2:入度为 2(来自 A1 和 A3)
- A3:入度为 1(来自 A2)
- A4:入度为 1(来自 A2)
- 从入度为 0 的属性开始遍历:
- 入度为 0 的属性是 A1。
- 从 A1 出发,根据 A1→A2 可到达 A2。
- 从 A2 出发,根据 A2→A3 和 A2→A4 可到达 A3 和 A4。
- 因此,从 A1 可以遍历所有节点(A2, A3, A4),说明 A1 能函数确定所有属性。
例题:关系模式P(A,B,C,D,E,F,G,H,I,J)满足下列函数依赖:FD={ABD→E,AB→G,B→F,C→J,CJ→1,G→H},求候选码?候选码为{A, B, C, D}。解题思路:
候选键(Candidate Key)和候选码(Candidate Code)是同一个概念,二者没有区别。
- 构建函数依赖有向图:

- 计算节点入度:
- A:入度为0(未出现在任何函数依赖的右边)
- B:入度为0(未出现在任何函数依赖的右边)
- C:入度为0(未出现在任何函数依赖的右边)
- D:入度为0(未出现在任何函数依赖的右边)
- E:入度为1(被ABD决定)
- F:入度为1(被B决定)
- G:入度为1(被AB决定)
- H:入度为1(被G决定)
- I:入度为1(被CJ决定)
- J:入度为1(被C决定)
- 检查入度为0的属性集({A, B, C, D})是否能遍历所有节点:
- 应用B→F,加入F → {A, B, C, D, F}
- 应用C→J,加入J → {A, B, C, D, F, J}
- 应用CJ→I,加入I → {A, B, C, D, F, I, J}
- 应用AB→G,加入G → {A, B, C, D, F, G, I, J}
- 应用G→H,加入H → {A, B, C, D, F, G, H, I, J}
- 应用ABD→E,加入E → {A, B, C, D, E, F, G, H, I, J}
Armstrong公理
Armstrong公理是一组用于推导函数依赖的逻辑规则,这些公理是数据库设计的基础,用于验证函数依赖的完备性和正确性,从而帮助实现规范化(如达到1NF、2NF、3NF、BCNF等)。
Armstrong公理由三条基本规则组成:
- 自反律(Reflexivity):若Y≤x≤U,则X→Y成立。任何属性集总是函数决定其子集。
- 增广律(Augmentation):若Z≤U且X→Y,则XZ→YZ成立。在函数依赖的两边同时增加相同的属性,依赖关系仍然成立。
- 传递律(Transitivity):若X→Y且Y→Z,则X→Z成立。函数依赖具有传递性。
从Armstrong公理可以推导出三条常用规则,这些规则简化了函数依赖的推导过程。

- 合并规则:由X→Y,X→Z,有X→YZ。(A2,A3)
- 伪传递规则:由X→Y,WY→Z,有XW→Z。(A2,A3)
- 分解规则:由X→Y及Z≤Y,有X→Z。(A1,A3)
范式判断
在不规范的数据库设计中,你可能会遇到以下问题:
- 数据冗余:相同的数据在多个地方重复存储,浪费空间。
- 更新异常:修改一个地方的数据后,必须同时修改其他所有重复的地方,否则会导致数据不一致。
- 插入异常:由于某些字段的缺失(如主键部分为NULL),无法插入某些信息。
- 删除异常:删除一条记录时,可能会意外地删除其他本应保留的信息。
规范化就是通过分解表结构来解决这些问题。

- 主属性:包含在任何一个候选码(候选键)中的属性。
- 非主属性:不包含在任何候选码(候选键)中的属性。
- 第一范式(1NF)
- 核心要求:确保每列都是原子的,不可再分。
- 判断方法:检查表中是否存在可再分的复合属性或多值属性(如“电话”字段存了“手机,家庭电话”)。可通过拆分到另一张表来解决多值问题。
- 第二范式(2NF)
- 前提:必须已经满足1NF。
- 核心要求:消除非主属性对候选码的“部分函数依赖”。
- 判断方法:找出表的所有候选码,找出所有非主属性。检查是否存在某个非主属性,只依赖于候选码的一部分(而不是全部)。(不存在部分依赖则属于第二范式)
示例:
- 不规范 (存在部分依赖):
- 表:选课记录 Score(学号, 课程号, 成绩, 学生姓名, 课程学分)
- 候选码:(学号, 课程号)
问题:学生姓名 只依赖于 学号(部分依赖);课程学分 只依赖于 课程号(部分依赖)。
- 符合2NF (分解后):
- Score(学号, 课程号, 成绩) // 成绩完全依赖于整个码
- Student(学号, 学生姓名) // 学生信息表
- Course(课程号, 课程学分) // 课程信息表
- 第三范式(3NF)
- 前提:必须已经满足2NF。
- 核心要求:消除非主属性对候选码的“传递函数依赖”。
- 判断方法:检查是否存在非主属性A依赖于非主属性B,而B又依赖于候选码(即 A → B → 候选码)。
示例:
- 不规范 (存在传递依赖):
- 表:学生信息 Student(学号, 姓名, 所在系, 系主任)
- 候选码:学号
- 函数依赖:学号 → 所在系;所在系 → 系主任;学号 → 系主任(传递依赖)。
- 符合3NF (分解后):
- Student(学号, 姓名, 所在系) // 学号 → 所在系
- Department(所在系, 系主任) // 所在系 → 系主任
- BC范式(BCNF / 3.5NF)
- 前提:必须已经满足3NF。
- 核心要求:消除主属性对候选码的部分和传递依赖。更严格的定义是:每个决定因素都必须包含候选码。
- 决定因素:在函数依赖 X → Y 中,X 称为决定因素。
- 判断方法:检查所有函数依赖的左边(决定因素)是否都是该表的候选码。
示例:
- 不符合BCNF: 假设一个简单的“学生-导师”场景:一个系有多个导师,一个导师只属于一个系,但一个导师可以带多个学生。
- 表:Teaching(学生, 导师, 系)
- 候选码:(学生, 系) 和 (学生, 导师)
- 函数依赖:
- 导师 → 系 // 一个导师属于一个系
- (学生, 系) → 导师
- (学生, 导师) → 系
问题:存在一个函数依赖 导师 → 系,它的决定因素导师不是候选码(它不能唯一标识一行记录)。
- 符合BCNF (分解后):
- Advisor(学生, 导师) // 候选码:(学生, 导师) 或 (学生)
- Faculty(导师, 系) // 候选码:导师, 导师 → 系
模式分解
模式分解是将一个低范式的关系模式分解成多个高范式关系模式的过程。
模式分解必须满足两个最重要的特性:
- 无损连接分解:
- 将分解后的子关系进行自然连接后,必须能完全恢复为原来的关系,既不丢失信息,也不产生多余的(原来不存在的)元组。
- 检验方法:对于分解 ρ = {R1, R2},如果 R1 ∩ R2 是 R1 或 R2 的超码,则该分解是无损连接的。不能还原为有损,可以还原为无损
- 保持函数依赖:
- 分解后,原关系模式中的所有函数依赖,应该能够由分解后的各个关系模式中的函数依赖逻辑蕴涵。
- 简单来说,就是原有的所有数据约束(依赖关系)不能因为分解而丢失。
最理想的分解是既满足无损连接,又满足保持函数依赖。
保持函数依赖分解例题:
- 例1:有关系模式R(A,B,C),F=[A→B,B→C},将其拆分为:R1(A,B),R2(B,C),是否保持函数依赖。
保持函数依赖。在分解后的关系模式中:
- R1(A, B) 保留了函数依赖 A → B,因为 A 是 R1 的候选键(A → B 成立)。
- R2(B, C) 保留了函数依赖 B → C,因为 B 是 R2 的候选键(B → C 成立)。
因此,所有原函数依赖都被分解后的关系模式所隐含,没有丢失任何依赖。
- 例2:有关系模式R(A,B,C),F=[A→B,B→C,A→C},将其拆分为:R1(A,B),R2(B,C),是否保持函数依赖。
分解后的关系模式:
- R1(A, B):保留函数依赖 A → B(因为 A 是 R1 的候选键)。
- R2(B, C):保留函数依赖 B → C(因为 B 是 R2 的候选键)。
分解后的函数依赖集为 {A → B, B → C}。从 A → B 和 B → C,通过传递性可以推导出 A → C。因此,原函数依赖集 F 中的所有依赖(包括 A → C)都被分解后的关系模式所隐含。
无损分解例题:
- 有关系模式:成绩(学号,姓名,课程号,课程名,分数),函数依赖:学号→姓名,课程号→课程名,(学号,课程号)→分数 若将其分解为:成绩(学号,课程号,分数)学生(学号,姓名)课程(课程号,课程名),请思考该分解是否为无损分解?
根据关系模式“成绩”(学号,姓名,课程号,课程名,分数)及其函数依赖(学号→姓名,课程号→课程名,(学号,课程号)→分数),分解为三个关系模式:
- 成绩(学号,课程号,分数)
- 学生(学号,姓名)
- 课程(课程号,课程名)
R1 和 R2 的交集属性是 {学号}。根据函数依赖,学号 → 姓名,因此学号是 R2 的候选键。 因此,R1 和 R2 的自然连接是无损的,得到临时关系 R12(学号,课程号,分数,姓名)。所以该分解是无损分解。
并发控制
数据库ACID属性:
- A - 原子性 (Atomicity)
- 定义:事务中的所有操作要么全部完成,要么全部不完成。不存在中间状态(即只执行了一半)。
- 比喻:就像原子是不可分割的一样,事务也是一个不可分割的工作单位。转账操作中,不能只扣A的钱而不加给B。
- 实现机制:通常通过日志来实现,特别是 Undo Log。如果事务失败,系统会使用 Undo Log 将已经执行的操作全部撤销,回滚到事务开始前的状态。
- C - 一致性 (Consistency)
- 定义:事务必须使数据库从一个一致性状态转换到另一个一致性状态。一致性状态是指数据必须满足预先定义的规则(如约束、触发器、级联等)。
- 比喻:转账前后,A和B账户的总金额应该保持不变。数据库的完整性约束(如账户余额不能为负数)也必须始终得到满足。 实现机制:这部分主要由应用程序和数据库的完整性约束来共同保证。原子性、隔离性和持久性是实现一致性的工具和手段。
- I - 隔离性 (Isolation)
- 定义:并发执行的事务之间不能相互干扰。一个事务的内部操作与其他并发事务是隔离的,它不应该看到其他并发事务的中间状态。
- 比喻:多个用户同时操作数据库,感觉就像是只有自己一个人在操作一样。
- 实现机制:这是并发控制主要要解决的问题。通过锁机制或多版本并发控制 (MVCC) 等技术来实现。不同的隔离级别提供了不同强度的隔离性。
- D - 持久性 (Durability)
- 定义:一旦事务提交,它对数据库所做的修改就是永久性的,即使发生系统故障(如断电、崩溃)也不会丢失。
- 比喻:就像用钢笔在纸上写字,写上去就擦不掉了(相对于铅笔)。
- 实现机制:通常通过 Redo Log 来实现。事务提交前,先将修改内容写入重做日志。即使数据页还没有写回磁盘,系统崩溃后重启也能通过重做日志重新执行(Redo)已提交的事务,从而保证持久性。
并发控制是实现隔离性的具体技术手段。当多个事务同时访问(读/写)同一数据时,如果不对其进行控制,就会导致一系列问题。
- 脏读 (Dirty Read):事务A读到了事务B未提交的数据。如果事务B后来回滚了,那么A读到的就是无效的“脏”数据。 简单说就是读了没提交的数据。(你读到的数据,是别人刚改了但还没最终确认的,他随时可能反悔撤回,你这个读操作就是白读了。)
- 不可重复读 (Non-repeatable Read):事务A内多次读取同一数据。在A执行期间,事务B修改并提交了该数据,导致A两次读取的结果不一致。 读了又读,值变了。(你在同一个操作里,前后两次读同一个东西,中间有别人把它改了并且生效了,导致你两次读到的结果不一样。)
- 幻读 (Phantom Read):事务A根据条件查询出一个结果集。此时事务B插入或删除了符合该条件的一条新数据并提交。事务A再次查询时,结果集的行数发生了变化,就像出现了“幻觉”一样。 读了又读,行没了/行了。(你第一次查出来10条结果,正准备处理呢,别人凭空新增了一条符合你条件的,或者删除了一条。你第二次再查,突然变成11条或者9条了,像见了鬼一样。)
- 丢失更新 (Lost Update):改了改,白改了。(你和别人同时改同一个东西,都以为自己是基于最新的数据在改。结果他先提交,你后提交,你的修改就把他的修改完全覆盖掉了,他的活算是白干了。)
为了在性能和数据一致性之间取得平衡,SQL标准定义了不同严格程度的隔离级别,允许开发者根据场景选择:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| 读未提交 | 可能 | 可能 | 可能 |
| 读已提交 | 不可能 | 可能 | 可能 |
| 可重复读 | 不可能 | 不可能 | 可能 |
| 序列化 | 不可能 | 不可能 | 不可能 |
级别从上到下越来越严格,并发性能也越来越低。读已提交和可重复读是最常用的级别。 Oracle 默认读已提交,MySQL InnoDB 默认可重复读。
并发控制的主要技术:
- 锁机制(容易产生死锁)
- 悲观锁:默认并发冲突会发生,因此在访问数据前先加锁。
- 共享锁 (S Lock):又称“读锁”。允许其他事务读,但不允许写。
- 排他锁 (X Lock):又称“写锁”。不允许其他事务读和写。
- 封锁协议:
- 一级封锁协议。事务T在修改数据R之前必须先对其加X锁,直到事务结束才释放。可防止丢失修改
- 二级封锁协议。一级封锁协议加上事务T在读取数据R之前先对其加S锁,读完后即可释放S锁。可防止丢失修改,还可防止读“脏”数据
- 三级封锁协议。一级封锁协议加上事务T在读取数据R之前先对其加S锁,直到事务结束才释放。可防止丢失修改、防止读“脏”数据与防止数据(不可)重复读
- 两阶段锁协议 (2PL):保证可串行化的一个重要协议。事务分为两个阶段:
- 加锁阶段:在对任何数据进行读/写操作之前,必须先申请并获得相应的锁。此阶段可以申请新锁,但不能释放任何锁。
- 解锁阶段:事务开始释放锁,此阶段只能释放锁,不能再申请新锁。
多版本并发控制:
- 核心思想:为了提高并发性能,MVCC 让读操作不再阻塞写操作,写操作也不再阻塞读操作。
- 实现原理:为每一行数据维护多个版本(快照)。当一个事务开始时,它看到的是数据库在某个时间点的一致性快照。即使其他事务后来修改并提交了数据,该事务看到的仍然是它开始时那个版本的数据。
- 如何实现快照:通常通过为每行数据添加两个隐藏字段来实现:
- 创建版本号:记录插入此行数据的事务ID。
- 删除版本号:记录删除此行数据的事务ID。
- 优点:读不加锁,读写不冲突,性能极高。
- 缺点:需要额外的存储空间来存储多个数据版本,并且需要维护和清理旧版本数据。
- 应用:MySQL 的 InnoDB 和 PostgreSQL 等都使用 MVCC 作为其主要并发控制机制。
数据库的安全性措施
数据库的安全性措施:
- 用户标识和鉴定:最外层的安全保护措施,可以使用用户帐户、口令及随机数检验等
- 存取控制:对用户进行授权,包括操作类型(如查找、插入、删除、修改等动作)和数据对象(主要是数据范围)的权限。(Grant和Revoke)
- 密码存储和传输:对远程终端信息用密码传输
- 视图的保护:对视图进行授权
- 审计:使用一个专用文件或数据库,自动将用户对数据库的所有操作记录
数据库备份与恢复
为什么需要备份与恢复?
- 数据备份:将数据库中的数据以某种格式复制到另一个存储介质的过程。它是数据的“安全副本”。
- 故障恢复:当数据库发生故障(如系统崩溃、磁盘损坏、人为误操作)后,利用备份和日志将数据库恢复到某个一致、正确状态的过程。
- 目标:确保数据的持久性和一致性,最大限度减少数据丢失,保障业务连续性。
数据备份
根据备份过程中数据库是否处于可提供服务的运行状态可划分:
- 冷备份:也称为静态备份,是将数据库正常关闭,在停止状态下,将数据库的文件全部备份(复制〉下来。 备份过程简单一致性强,但业务中断,停机时间长。
- 热备份:也称为动态备份,是利用备份软件,在数据库正常运行的状态下,将数据库中的数据文件备份出来。 业务无中断,高可用。但技术复杂,可能对性能有轻微影响,需要数据库支持。
根据备份的数据范围可划分为:
- 全量备份:在某个时间点,对数据库所有数据进行一次完整的备份。它是所有备份的基础。
- 优点:恢复速度快且简单:只需要一份备份文件即可完成恢复,无需合并其他备份。数据完整性好:单份备份即包含所有数据,易于管理。
- 缺点:备份速度慢:每次都需要读写全部数据。占用存储空间大:保留多份全量备份会消耗大量存储资源。
- 适用场景:通常作为备份策略的基石,定期执行(例如每周一次)。
- 增量备份:备份自上一次备份操作以来(无论是全量、差异还是增量)发生变化的数据。
- 优点:备份速度最快:每次只处理变化的数据块。占用存储空间最小:备份集通常很小。
- 缺点:恢复速度最慢且最复杂:恢复时需要先恢复最近的一次全量备份,然后按时间顺序依次恢复所有后续的增量备份。任何一个增量备份损坏都会导致整个恢复链失败。
- 适用场景:数据变化频率不高,但对备份窗口(备份所需时间)要求非常严格的场景。
- 差异(差量)备份:备份自上一次全量备份以来所有发生变化的数据。
- 优点:恢复速度比增量备份快:恢复时只需要一份全量备份和最新的一份差异备份。占用空间比全量备份小。
- 缺点:备份速度比增量备份慢:随着时间推移,自上次全量备份后的变化会越来越多,差异备份的大小会逐渐增大(直到下一次全量备份为止)。占用空间比增量备份大。
- 适用场景:在备份速度和恢复复杂度之间寻求平衡的场景,是兼顾效率和可靠性的折中方案。
常见策略组合:
- 全量 + 增量:例如,每周日一次全量备份,周一至周六每天一次增量备份。
- 全量 + 差异:例如,每周日一次全量备份,周一至周六每天一次差异备份。
数据故障与恢复
| 故障类型 | 描述与常见原因 | 恢复机制与解决方法 | 核心依赖 |
|---|---|---|---|
| 可预期故障 | 由应用程序逻辑引发的错误,如:余额不足、违反业务规则、用户主动取消。 | 在应用程序中编写异常处理代码,发现错误后立即执行 ROLLBACK 语句,回滚当前事务。 |
应用程序逻辑 |
| 不可预期故障 | 非程序逻辑本身、难以预料的错误,如:算术溢出、死锁 被系统强制终止、违反唯一性/外键约束。 | 数据库恢复子系统自动检测到事务失败。利用撤销日志 (UNDO Log),撤销该事务对数据库所做的一切修改,回退到事务开始前的状态。 | 事务日志 (UNDO) |
| 系统故障 (软故障) | 导致系统停止运转的故障,但未破坏磁盘数据。如:操作系统崩溃、DBMS进程意外终止、服务器突然断电、主存丢失。 | 1. 未完成的事务:系统重启后,根据日志撤销 (UNDO) 所有未提交的事务。 2. 已提交但未落盘的事务:根据日志重做 (REDO) 所有已提交但更新可能还在内存缓冲区、未写入数据文件的事务。 检查点 (Checkpoint) 技术极大地优化了这一过程。 |
事务日志 (REDO/UNDO) 检查点 |
| 介质故障 (硬故障) | 外存设备故障,导致磁盘上的数据文件或日志本身被破坏。如:磁盘控制器故障、磁头损坏、强磁场干扰、操作员的误删除。 | 1. 装入最新的备份:恢复最近的一个物理备份(冷备或热备)。 2. 重做已完成的事务:装入备份后,重新应用自该备份以来的所有归档日志 (Archive Log) 和重做日志 (Redo Log),将数据库前滚到故障发生前的最近一致状态。 |
最新备份 归档日志/重做日志 |
| 人为逻辑故障 | 操作人员无意或恶意的错误操作,如:误执行了 DELETE FROM table 或 DROP TABLE,且已提交。 |
时间点恢复 (PITR - Point-In-Time Recovery): 1. 从备份中恢复数据库。 2. 应用日志,但仅重放到误操作发生之前的那个时间点。这样既恢复了数据,又“跳过”了错误的操作。 |
最新备份 日志 (需开启归档) |
数据库性能优化
数据库性能优化是一个系统性工程,旨在通过一系列技术和管理手段,减少系统资源消耗、加快查询响应速度、提高吞吐量,从而确保数据库能够高效、稳定地支撑业务需求。其核心目标是在有限的硬件资源下,实现更高的并发处理能力和更低的延迟。
范式和反规范化
范式化是通过一系列设计规则(第一范式1NF、第二范式2NF、第三范式3NF等)来消除数据冗余,保证数据逻辑一致性的过程。
相同的数据只存储在一个地方(例如,客户姓名只存储在Customers表,而不是每个订单里都存一遍)。节省存储空间。 由于数据只存一份,更新时只需修改一处,避免了重复数据可能带来的不一致性(例如,修改客户姓名时,只需改Customers表,而不需要去更新成千上万条订单记录)。 所以它结构清晰,易于理解和维护。但是为了获取完整的信息,经常需要进行多表连接(JOIN)。当数据量巨大时,复杂的JOIN操作会成为性能瓶颈。
反规范化是故意地向表中添加冗余数据,或者将多个表合并成一个表,目的是为了避免昂贵的JOIN操作,从而加快读取速度。
- 数据冗余:占用更多的磁盘空间。
- 数据不一致风险:冗余的数据需要在多个地方更新。如果更新过程中出现错误或遗漏,就会导致同一份数据在不同位置的值不一样。
- 增加写操作成本和复杂性:每次INSERT, UPDATE, DELETE操作都可能需要更新多个地方的冗余数据,这会降低写入性能,并使应用程序逻辑更复杂。
在实际项目中,很少会极端地全部范式化或全部反规范化。正确的做法是先范式化,再基于性能需求有选择地反规范化。 当系统上线后,通过性能监控(如慢查询日志)找出那些频繁执行且速度很慢的查询。针对这些具体的查询场景,考虑反规范化策略。常用技术:
- 增加派生性冗余列:在表中增加一个由同一表中其他列计算得出的列。
- 增加冗余列:在“多”的一方添加“一”的一方的属性,避免JOIN。
- 重新组表:将多个通过外键关联的表(通常是1:1或1:N关系)合并成一张更宽的“大表”。
- 分割表(水平分割/分片):将一张大表中的数据按照某种规则分布到多个结构相同的物理子表中。
数据库索引
索引是帮助数据库系统高效获取数据的数据结构。它就像数据的“目录”,存储了特定列的值及其对应数据行的物理地址(如行ID、指针)。
索引的核心价值在于将随机的 I/O(全表扫描,磁盘寻道时间长)变为顺序的 I/O(按索引读取,效率高),并通过高效的数据结构(如B+树)极大减少需要扫描的数据量。
当没有索引时,SELECT * FROM users WHERE name = 'Alice';
数据库必须逐行扫描整个users表,检查每一行的name字段是否为‘Alice’。对于百万级数据的表,这非常缓慢。
有索引,在name字段上。数据库会去name列的索引数据结构中快速查找‘Alice’。
找到后,索引会直接告诉数据库‘Alice’这条记录存储在磁盘上的哪个位置(物理地址),数据库然后直接去那个位置读取数据即可,速度极快。
不同的数据库和场景会使用不同的数据结构,最常见的是:
- B+树索引:最常用的索引类型。MySQL的InnoDB、PostgreSQL等主流数据库的默认索引都是B+树。特点是多路平衡搜索树,矮胖,层级少。所有数据都存储在叶子节点,且叶子节点之间通过指针相连,形成有序链表。
- 哈希索引:基于哈希表实现,对于每一行数据,计算索引列的哈希码作为键。等值查询极快,时间复杂度接近O(1)。但完全不支持范围查询。无法用于排序。哈希冲突也会影响性能。
数据库视图
视图是一个基于 SQL 查询结果集的虚拟表。其内容由查询定义。 同真实的表一样,视图包含一系列带有名称的列和行数据。但是,视图并不在数据库中以存储的数据值集形式存在,除非是物化视图。
视图优点:
- 视图能简化用户的操作。
- 视图机制可以使用户以不同的方式查询同一数据。
- 视图对数据库重构提供了一定程度的逻辑独立性。
- 视图可以对机密的数据提供安全保护。
为了解决普通视图的性能开销问题,引入了物化视图。
- 与普通视图的区别:物化视图是真正存储数据的。它会将查询结果实际地物理存储在一张表中。
- 优点:查询速度极快,因为不需要实时计算,直接查询存储的结果即可。
- 缺点:数据不是实时更新的,需要定期刷新来与基表保持同步,因此数据具有一定延迟性。
- 适用场景:数据仓库、BI报表系统等对查询性能要求极高,但对数据实时性要求不高的场景。
物化视图的刷新策略:
- 完全刷新:执行
REFRESH MATERIALIZED VIEW view_name;- 工作原理:彻底清空物化视图中的数据,然后重新执行其背后的查询语句,将最新的结果完整地插入进去。
- 优点:简单可靠,总能得到最新数据。
- 缺点:非常耗时耗资源,尤其是基表数据量很大时。在刷新过程中,视图可能无法被查询或会被锁定。
- 增量刷新:这是更高级的模式。数据库会通过日志来追踪自上次刷新后基表发生的所有变化(增、删、改),然后只将这些变化同步到物化视图中。
- 优点:速度非常快,资源消耗小。
- 缺点:实现复杂,并非所有数据库都支持。例如,Oracle 支持此功能,而 PostgreSQL 的物化视图本身不支持增量刷新(但可以通过一些技巧和第三方工具实现)。
- 自动定时刷新:大多数数据库支持将刷新操作配置为定时任务,例如,每天凌晨2点执行一次完全刷新,从而实现“近乎同步”的效果。
分库、分表、分区
随着数据量和访问量的增长,单个数据库服务器会遇到存储容量、CPU处理能力、内存使用和磁盘I/O等方面的瓶颈。 “分”的核心思想是将集中式的数据库拆分成更小、更易管理的部分,分散到不同的资源上,从而提升系统的整体性能、可用性和可扩展性。
分区:将一张逻辑上的大表,在数据库内部按照特定规则,分割成多个更小的、物理上的子部分文件(分区),但这些分区对应用程序来说仍然是一张表,访问是透明的。
- 划分策略:
- 范围分区:按某个值的范围划分(如按日期:2023-01分区, 2023-02分区)。
- 列表分区:按某个值的离散列表划分(如按地区:北京分区, 上海分区)。
- 哈希分区:根据哈希函数的值均匀分布数据。
- 优点:应用程序无需修改代码,像使用普通表一样操作。可以快速删除整个分区(如删除历史数据),效率远高于DELETE。如果查询条件包含分区键,数据库可以只扫描相关的分区(分区裁剪),极大提升性能。
- 缺点:所有分区仍在同一台服务器的同一个数据库实例中,无法解决CPU、内存、I/O的硬件瓶颈。对于海量数据和高并发,提升有限。
- 适用场景:单表数据量巨大,但数据库服务器硬件资源尚未完全吃透。需要频繁按特定维度(如时间)删除历史数据。
分表:将一张逻辑上的大表,拆分成多个结构相同的物理表(子表),并分散到一个或多个数据库的多个实例中。应用程序需要知道数据存储在哪个具体的表中,或通过中间件来管理路由。
- 划分策略:
- 水平分表:最常用的方式。将表中的行按规则拆分到不同的表中。所有表的结构完全一致。
- 垂直分表:将表中的列拆分到不同的表中。将不常用的大字段分出去。
- 优点:真正将数据和负载分散到多台机器上。大幅减少单表数据量,降低索引树高度,提高读写效率。单个数据库实例故障,只影响部分数据。
- 缺点:需要修改代码或引入中间件来处理路由(如根据user_id决定查哪个表)。JOIN操作、聚合查询(COUNT, SUM)、排序分页变得极其复杂,通常需要在应用层做合并。难以保证跨多个分片的事务一致性,通常需要引入更复杂的方案(如XA事务、TCC等)
- 适用场景:单表数据量已远超单机硬盘容量。数据库的写操作(INSERT/UPDATE/DELETE)并发量极高,单机已无法承受。公司有专业的中间件和运维团队来管理分片逻辑。
分库:将整个数据库(包括其中的多张表)作为一个单元,拆分成多个小的数据库,并部署到不同的数据库实例上。它通常是分表的伴随操作,分表后,将这些表分布到不同的数据库中,自然就形成了分库。
- 划分策略:与水平分表的策略一致,通常按业务维度划分。
- 按业务功能:将用户相关表放在用户库,订单相关表放在订单库(这也可称为垂直分库)。
- 按数据范围:将2023年的数据放在DB_2023,2024年的数据放在DB_2024。
- 优点:承接分表的所有优点。每个数据库实例独立处理连接,避免了单机连接数瓶颈。一个数据库实例宕机,不会影响其他实例上的业务。
- 缺点:承接分表的所有缺点,且复杂度更高。原本在同一个数据库中可以轻松完成的表关联,在分库后变得几乎不可能。跨库事务保证更难。
- 适用场景:在需要进行分表的同时,数据库连接数也达到瓶颈。需要按照业务模块进行彻底隔离,方便微服务化架构。
NoSQL
NoSQL(Not Only SQL)泛指非关系型数据库。它是一种用于存储和检索数据的数据库管理系统,其数据模型与传统的关系型数据库(SQL)所使用的表格关系模型截然不同。
| 对比维度 | 关系型数据库 | NoSQL 数据库 |
|---|---|---|
| 数据模型 | 结构化数据,基于严格的二维表(行和列)模型,需要预定义 Schema。 | 灵活的数据模型,支持非结构化和半结构化数据。常见模型有:文档、键值、列族、图。无需或可动态定义 Schema。 |
| 应用领域 | 面向通用领域,适用于需要复杂查询、事务一致性高的场景(如金融系统、ERP、CRM)。 | 针对特定应用领域和访问模式高度优化(如社交网络、物联网、内容管理)。 |
| 数据容量 | 支持 TB 级别,但在海量数据下性能和可管理性会下降。 | 专为海量数据(PB 级别)设计,易于管理。 |
| 查询语言 | 使用标准化的 SQL,功能强大,支持复杂的连接、聚合和嵌套查询。 | 没有统一的查询语言。不同数据库使用不同的 API 和查询方式(如 MongoDB 使用 BSON,Cassandra 使用 CQL)。对复杂查询(尤其是 JOIN)支持较弱。 |
| 并发支持 | 支持并发,但在写入密集型高并发场景下,性能较低(由于锁机制和事务保证)。 | 为高并发读写而生,尤其在写入场景下,性能极高。 |
| 事务支持 | 强一致性,完全支持 ACID 事务,保证数据完整性。 | 通常支持弱事务性(最终一致性),遵循 BASE 原则。牺牲强一致性以换取可用性和性能。部分新型 NoSQL 数据库也开始支持跨文档 ACID 事务。 |
| 扩展方式 | 向上扩展:通过增加单个服务器的 CPU、内存、存储来提升性能(Scale-Up)。成本高且有上限。 | 向外扩展:通过增加廉价的普通服务器节点来分散负载(Scale-Out)。成本低,扩展性强,是分布式架构的首选。 |
| 标准化 | 高度标准化,有统一的语言和操作规范,不同产品之间迁移相对容易。 | 缺乏统一标准,不同产品的数据模型、API、功能差异很大。 |
| 一致性 | 强一致性:在任何时刻,所有用户看到的数据都是一致的。 | 取决于产品配置,通常提供最终一致性:数据更新后,经过一段时间的传播,所有副本最终会保持一致。 |
| 典型产品 | MySQL, PostgreSQL, Oracle, SQL Server | MongoDB (文档), Redis (键值), Cassandra (列族), Neo4j (图) |
联邦数据库
联邦数据库 并不是一个全新的物理数据库,而是一种虚拟的数据库集成系统。
它的核心思想是:在不移动、不复制、不改变原始数据的前提下,提供一个统一的SQL查询接口,将分布在不同物理位置、不同类型的数据源(如多个MySQL、Oracle、SQL Server、甚至文件或NoSQL数据库)逻辑地整合成一个单一的、虚拟的数据库视图。

数据库性能优化

集中式数据库优化:
- 硬件系统优化
- CPU:升级更多核心、更高主频的CPU处理高并发计算任务
- 内存:扩大内存容量,使热数据和索引常驻内存,减少磁盘I/O
- I/O子系统:
- 使用SSD替代机械硬盘
- 配置RAID(如RAID 10)提升I/O性能和数据可靠性
- 网络:确保足够带宽和低延迟,避免成为瓶颈
- 系统软件优化
- 参数调优:调整OS和数据库参数,优化资源分配
- 进程优先级/CPU使用权:确保数据库进程获得足够CPU资源
- 内存使用:合理配置数据库内存使用上限
- 参数调优:调整OS和数据库参数,优化资源分配
- 数据库设计优化
- 表的规划:遵循范式设计,必要时反规范化(增加冗余字段)
- 物化视图:将复杂查询结果预先计算并物理存储,用空间换时间
- 索引优化
- 常查询-建索引:为WHERE/JOIN/ORDER BY频繁使用的列创建索引
- 常修改-避免索引:写多读少的列避免过多索引,减少维护开销
- 应用软件(SQL)优化
- 以不相干子查询替代相干子查询:重写SQL减少子查询执行次数
- 只检索需要的列:避免SELECT *,减少网络传输和数据处理
- 用IN替换OR:对索引列使用IN条件提高查询效率
- 经常提交COMMIT:及时释放锁,提高并发性能
- 减少多表查询:优化复杂JOIN,考虑业务层处理或冗余字段
分布式数据库优化:
- 通信代价
- 核心目标:减少节点间网络传输数据量
- 全局查询树的变换
- 优化思路:选择跨节点执行成本最低的查询计划
- 多副本策略
- 优化思路:数据冗余存储,读操作访问最近副本,提升读性能
- 查询树的分解
- 优化思路:将查询分解为可并行执行的子查询,提高并行度
- 半连接与直接连接
- 半连接:先过滤后传输,减少连接操作中的网络传输
- 直接连接:同一节点上的表直接连接,避免跨网络传输