AIGC标识 3.6 关系数据库的规范化

数据库规范化与并发控制:一份修正后的完整笔记

本篇整理自常见教材(如王珊《数据库系统概论》)的核心知识点,修正了若干易错表述,并为每条关键概念配了通俗样例。可直接作为复习提纲或博客正文使用。


一、函数依赖(Functional Dependency)

1. 定义

设关系模式 R(U),X、Y 是 U 的子集。若 R 中任意两个元组在 X 上的值相等时,它们在 Y 上的值也必然相等,则称 "X 函数确定 Y" 或 "Y 函数依赖于 X",记作 X -> Y。

注意:函数依赖是语义层面的约束(由现实世界规则决定),而不是某个具体的数学公式。像 "Y = X * X" 这种算术表达式并不是函数依赖的例子;正确的例子是「学号 -> 姓名」——因为同一个学号只能对应一个学生姓名。

更多通俗样例:

  • 「身份证号 -> 姓名」:同一个人不可能有两个名字。
  • 「手机号 -> 用户名」:一个手机号只注册一个账号。
  • 「商品编号 -> 商品名称」:同一款商品只有一个名字。
  • 「车牌号 -> 车主姓名」:一辆车登记在一个人名下。

反例(不成立的情况):

  • 「姓名 -> 学号」不成立,因为可能有两个同名学生。
  • 「成绩 -> 学生姓名」不成立,因为不同学生可能考相同分数。

2. 平凡与非平凡

  • 平凡函数依赖:若 Y 包含于 X,则 X -> Y 必然成立(例如 (学号, 姓名) -> 学号)。这类依赖不提供任何新信息。
  • 非平凡函数依赖:Y 不包含于 X。我们讨论的依赖基本都是这种。

通俗理解:平凡依赖就像说"我的身份证号码+姓名 能确定我的身份证号码"——这是废话,因为左边已经包含了右边。真正有价值的是非平凡依赖,比如"学号能确定姓名"。

3. 完全(直接)函数依赖 vs 部分函数依赖

设 X -> Y,且 X 是复合属性集:

  • 若存在 X 的真子集 X' 使得 X' -> Y 成立,则称 Y 对 X 部分函数依赖;
  • 若对 X 的任何真子集都不成立,则称 Y 对 X 完全函数依赖。

样例:选课表 SC(学号, 课程号, 姓名, 成绩),候选键为 (学号, 课程号)。

  • (学号, 课程号) -> 成绩 是完全依赖(缺一门就定不下成绩,必须知道是哪门课);
  • (学号, 课程号) -> 姓名 是部分依赖(光靠 学号 就能定姓名,课程号 是多余的)。

更多通俗样例:

场景 复合键 完全依赖 部分依赖
仓库出货 (仓库号, 商品号) -> 出货数量 出货数量(需同时知道哪个仓库、哪个商品) 商品名称(知道商品号就能确定名称)
员工考勤 (员工号, 日期) -> 打卡时间 打卡时间(需同时知道谁、哪天) 员工部门(知道员工号就能确定部门)
订单明细 (订单号, 商品编号) -> 单价 单价(需同时知道哪个订单、哪种商品) 商品名称(知道商品编号就能确定名称)

4. 传递函数依赖

若 X -> Y,Y -> Z,且满足 Y 不包含于 X 以及 Y 不能决定 X(即 X 与 Y 不等价),则称 Z 对 X 存在传递函数依赖。

样例:学生(学号, 系名, 系主任),其中 学号 -> 系名,系名 -> 系主任,而 系名 不能决定 学号(一个系有很多学生)。因此 学号 -> 系主任 是传递依赖。

更多通俗样例:

  • 「员工号 -> 部门号 -> 部门经理」:通过员工号找到部门,再通过部门找到经理。员工号不能通过部门号反推(一个部门多个员工),所以这是传递依赖。
  • 「快递单号 -> 物流站点 -> 站点所在城市」:通过单号找到经手站点,再通过站点找到所在城市。这是传递依赖。

补充:如果 X 与 Y 等价(互相决定),那么 X -> Z 属于直接决定,不算传递。例如若 学号 <-> 工号(一人一号,一号一人),则 学号 -> 姓名 和 工号 -> 姓名 是直接关系,不存在传递。


二、Armstrong 公理系统与推理规则

设 U 为属性全集,F 是 R 上的函数依赖集。

1. 三条基本公理

规则 内容 通俗样例
自反律 若 Y 包含于 X,则 X -> Y 知道"学号+姓名"就能知道"学号"——这很明显,因为右边本来就是左边的一部分
增广律 若 X -> Y 成立,且 Z 是任意属性集,则 XZ -> YZ 成立 已知"学号 -> 姓名",两边同时加上"课程号",得到"学号+课程号 -> 姓名+课程号"
传递律 若 X -> Y、Y -> Z 成立,则 X -> Z 成立 已知"学号 -> 系名"且"系名 -> 系主任",所以"学号 -> 系主任"

2. 三条常用推论(可由上面三条推出)

规则 内容 通俗样例
合并律 若 X -> Y、X -> Z 成立,则 X -> YZ 成立 已知"学号 -> 姓名"和"学号 -> 系名",可以合并为"学号 -> 姓名+系名"
分解律 若 X -> YZ 成立,则 X -> Y、X -> Z 都成立 已知"学号 -> 姓名+系名",可以拆成"学号 -> 姓名"和"学号 -> 系名"
伪传递律 若 X -> Y、WY -> Z 成立,则 XW -> Z 成立 已知"学号 -> 系名"和"系名+课程号 -> 成绩",可得"学号+课程号 -> 成绩"

3. 属性集闭包

X 关于 F 的闭包 X+:从 X 出发,利用 F 中的依赖能推导出的所有属性的集合。

用途:判断 X -> Y 是否成立,只需验证 Y 包含于 X+。

样例:F = {A->B, B->C, C->D},则 A+ = {A,B,C,D},故 A -> D 成立。

更多通俗样例:

设学生表属性 U = {学号, 姓名, 系名, 系主任, 课程号, 成绩},依赖集 F = {学号->姓名, 学号->系名, 系名->系主任, (学号,课程号)->成绩}。

  • 求 {学号}+:从学号出发,能推出姓名、系名,再由系名推出系主任。所以 {学号}+ = {学号, 姓名, 系名, 系主任}。注意:成绩推不出来,因为还需要课程号。
  • 求 {(学号,课程号)}+:从学号和课程号出发,能推出成绩,再由学号推出姓名、系名、系主任。所以 {(学号,课程号)}+ = 全部属性 = 候选键。
  • 判断"学号 -> 系主任"是否成立:{学号}+ 包含系主任,所以成立。(这也验证了传递依赖)

4. 最小函数依赖集(最小覆盖)

满足三个条件:① 右部均为单属性;② 无冗余依赖(删掉任一依赖,闭包会变小);③ 左部无冗余属性。它是规范化分解前的常用预处理步骤。

通俗样例:

假设 F = {学号->姓名, 学号->系名, 学号->系主任, 系名->系主任}。

  • 第一步(右部分解):已经都是单属性,不用改。
  • 第二步(去冗余依赖):学号->系主任 可以通过 学号->系名 + 系名->系主任 推导出来,是冗余的,删掉。
  • 第三步(去左部冗余属性):假设有一条 (学号,课程号)->成绩,检查学号能否单独决定成绩——不能,所以左部无冗余。

最终最小覆盖:F' = {学号->姓名, 学号->系名, 系名->系主任}。


三、键(Key)与属性分类

概念 定义 通俗样例
超键 能唯一标识元组的属性集(不要求最小) (学号, 姓名) 能唯一确定一个学生,是超键,但学号本身就够了,姓名是多余的
候选键 极小超键:去掉任一属性就不再唯一 学号 是候选键(去掉就什么都不是了);(学号, 手机号) 也是候选键(两个合起来唯一,但每个单独不一定唯一)
主键 从候选键中选定一个作为主键 实际建表时选 学号 做 PRIMARY KEY
外键 本表中的一个(组)属性,其取值必须匹配另一张表(或自身)的候选键的值,或者为空 选课表中的 学号 参照学生表的 学号;员工表中的 上级编号 参照同表的 员工编号
主属性 属于任一候选键的属性 若候选键有 学号 和 手机号,则 学号 和 手机号 都是主属性
非主属性 不属于任何候选键的属性(也叫非码属性) 姓名、成绩、系名 等

更多通俗样例(以公司系统为例):

假设有一张员工表 Employee(工号, 邮箱, 姓名, 部门号, 部门名称, 上级工号):

  • 超键:{工号, 邮箱}、{工号, 姓名}、{工号, 邮箱, 姓名} 等都是超键(能唯一标识一个员工)
  • 候选键:{工号} 和 {邮箱}(假设邮箱唯一),因为它们极小——去掉任何一个属性就不能唯一标识了
  • 主键:选 {工号} 作为主键(工程上通常选自然键或代理键)
  • 外键:部门号 参照 Department 表的 部门号;上级工号 参照本表的 工号
  • 主属性:工号、邮箱(它们属于候选键)
  • 非主属性:姓名、部门名称、上级工号

求候选键的方法:

方法一:闭包枚举法(通用)

  1. 只在依赖左部出现、不在右部出现的属性,一定属于每个候选键;
  2. 只在右部出现的属性,一定不属于任何候选键;
  3. 两边都出现的属性,逐个组合试闭包,若闭包 = U 且极小,即为候选键。

方法二:有向图法(直观但有局限)

把属性当节点,按 X -> Y 画有向边。入度为 0 的节点必属候选键;从它们出发能遍历全图的极小组合即为候选键。

局限:当依赖右部为多属性、或存在组合决定的情况时,图法可能漏解,建议用闭包法复核。

样例:R(A,B,C,D),F={A->B, B->C, C->D, D->A}。每个属性闭包都是 {A,B,C,D},故候选键有四个:A、B、C、D,全部是主属性。


四、完整性约束

类型 含义 通俗样例
实体完整性 主键的各属性不能取空值,且不能重复 学号不能为 NULL、不能有两个学生学号都是 2024001
参照完整性 外键的值必须是参照表中已存在的候选键值,或者为 NULL 选课表里填的学号,必须在学生表里能找到对应的人
用户自定义完整性 根据应用语义自定义的约束条件 年龄 BETWEEN 0 AND 150;性别 IN ('男','女');邮箱必须包含 @ 符号
触发器(Trigger) 通过编写过程代码,在 INSERT/UPDATE/DELETE 前后自动执行,可实现复杂约束或业务逻辑 删除某商品前,自动检查库存表中是否还有该商品的记录,若有则阻止删除并提示

更多通俗样例:

  • 实体完整性:电商系统中,订单号不能为空也不能重复——否则系统不知道这笔订单是谁的。
  • 参照完整性:订单表中的 客户ID 必须是客户表中已存在的客户,不能给一个不存在的客户下单。
  • 用户自定义完整性:注册时密码长度不少于 8 位;日期不能是未来时间(如出生日期不能是 2030 年)。
  • 触发器:
    • 每次修改库存后,自动在"操作日志表"中记录谁在什么时候改了什么;
    • 员工离职时,自动将其名下所有项目转移给部门经理;
    • 订单金额超过 1 万元时,自动触发"风控审核"流程。

说明:触发器常用于实现用户自定义完整性,但它还能做审计、同步、日志等工作,所以更准确的说法是"实现复杂约束的手段之一",而不宜断言"本质属于用户自定义完整性约束"。


五、范式(Normalization)

范式层级:1NF ⊂ 2NF ⊂ 3NF ⊂ BCNF ⊂ 4NF ⊂ 5NF

1NF:属性不可再分

每个分量都必须是原子值,表中不能再套表(不允许"组合项"、"重复组")。

通俗理解:1NF 就是"表格不能嵌套表格"。每一格只能填一个值,不能填一堆值。

违反 1NF 的例子:

学号 姓名 选修课程
001 张三 数学, 英语, 物理

修正后(拆成多行):

学号 姓名 课程
001 张三 数学
001 张三 英语
001 张三 物理

1NF 仍存在四大问题(以 学生(学号, 姓名, 系名, 系主任, 课程号, 成绩) 为例):

  • 数据冗余:同一系的学生反复存储相同的系主任——就像全班同学的档案里都抄了一遍系主任名字,浪费空间还容易不一致。
  • 更新异常:换系主任要改该系所有学生的记录——如果改了其中一个忘了改另一个,数据就不一致了。
  • 插入异常:新系成立但还没学生时,系信息无法录入——因为主键是学号,没有学生就没有学号,系的信息就无处安放。
  • 删除异常:某系学生全部毕业被删后,该系信息也随之丢失——就像把整个班级删了,系主任信息也跟着没了。

2NF:消除非主属性对候选键的部分依赖

若 R 属于 1NF,且每个非主属性都完全函数依赖于每一个候选键,则 R 属于 2NF。

通俗理解:2NF 就是"非主属性必须跟整个主键有关系,不能只跟主键的一部分有关系"。

接上例:候选键为 (学号, 课程号),而 学号 -> 姓名、学号 -> 系名 是部分依赖(只依赖主键的一部分——学号),故不满足 2NF。

分解为:

  • 学生(学号, 姓名, 系名, 系主任) —— 主键 学号
  • 选课(学号, 课程号, 成绩) —— 主键 (学号, 课程号)

两张表均满足 2NF(选课 中成绩完全依赖于整个主键——必须同时知道哪个学生、哪门课才能确定成绩)。

更多通俗样例(电商订单场景):

原始表 OrderInfo(订单号, 商品号, 商品名称, 商品单价, 数量, 总金额, 客户姓名, 客户地址):

  • 候选键 = (订单号, 商品号)
  • 部分依赖:商品号 -> 商品名称、商品号 -> 商品单价(不需要订单号就能确定)
  • 完全依赖:(订单号, 商品号) -> 数量、(订单号, 商品号) -> 总金额

分解后:

  • 订单明细(订单号, 商品号, 数量, 总金额) —— 主键 (订单号, 商品号)
  • 商品信息(商品号, 商品名称, 商品单价) —— 主键 商品号
  • 客户信息(订单号, 客户姓名, 客户地址) —— 主键 订单号

3NF:消除非主属性对候选键的传递依赖

若 R 属于 2NF,且不存在非主属性对候选键的传递依赖,则 R 属于 3NF。
等价定义:对每个非平凡函数依赖 X -> A,要么 X 是超键,要么 A 是主属性。

通俗理解:3NF 就是"非主属性之间也不能通过其他非主属性间接挂钩"。

接上例:学生 表中 学号 -> 系名 -> 系主任,存在传递依赖(系主任通过系名间接依赖于学号),故不满足 3NF。

再分解为:

  • 学生(学号, 姓名, 系名) —— 主键 学号
  • 系(系名, 系主任) —— 主键 系名
  • 选课(学号, 课程号, 成绩) —— 主键 (学号, 课程号)

三张表均满足 3NF。

更多通俗样例(员工场景):

原始表 Employee(工号, 姓名, 部门号, 部门名称, 部门经理, 工资):

  • 候选键 = 工号
  • 传递依赖:工号 -> 部门号 -> 部门名称 -> 部门经理

分解后:

  • 员工(工号, 姓名, 部门号, 工资) —— 主键 工号
  • 部门(部门号, 部门名称, 部门经理) —— 主键 部门号

BCNF:消除主属性对键的部分/传递依赖

若 R 属于 1NF,且对每个非平凡函数依赖 X -> Y,X 必为超键(决定因素都包含候选键),则 R 属于 BCNF。

通俗理解:BCNF 比 3NF 更严格——不仅非主属性不能有传递依赖,主属性也不能有。换句话说,"谁能决定其他东西"这个权力,必须只有候选键(或超键)才配拥有。

经典反例:授课(学生S, 课程C, 教师T),F = {(S,C)->T, T->C}。

  • 候选键:(S,C) 和 (S,T) → 三个属性全是主属性 → 已满足 3NF(因为没有非主属性,3NF 的条件自动满足);
  • 但 T -> C 中 T 不是超键 → 不满足 BCNF(一个非超键决定了另一个属性)。

通俗理解这个反例:一个教师只能教一门课(T->C),但一门课可以有多个老师教。学生选某门课的某个老师就能确定成绩。这里"教师"这个主属性(因为它出现在候选键(S,T)中)决定了"课程",但教师本身不是候选键——这就违反了 BCNF。

分解为 BCNF:教课(S, T) 与 任课(T, C)。

更多通俗样例:

假设关系模式 R(学生, 课程, 教室),语义规则:

  • 一个学生在同一时间只能上一门课,且每门课固定在一个教室 → (学生, 课程) -> 教室
  • 每门课固定在同一个教室 → 课程 -> 教室

候选键有 (学生, 课程) 和 (学生, 教室)。课程是主属性(出现在候选键(学生,教室)中),但 课程 -> 教室 中 课程 不是超键,所以不满足 BCNF。

分解为:选课(学生, 课程) 和 排课(课程, 教室)。

4NF:消除非平凡的多值依赖

多值依赖:设 X, Y, Z 是 U 的子集,Z = U - X - Y。若给定一对 (x, z) 值,对应的 Y 值只由 x 决定、与 z 无关,则称 X ->-> Y(Y 多值依赖于 X)。

通俗理解:多值依赖描述的是"一对多"且"相互独立"的关系。比如一门课有多个老师和多本参考书,老师的选择和参考书的选择互不影响——选哪个老师不影响有哪些参考书,反之亦然。

性质:对称性(X ->-> Y 则 X ->-> Z)、传递性、函数依赖是多值依赖的特例(X -> Y 则 X ->-> Y)。

R 属于 1NF,若对每个非平凡多值依赖 X ->-> Y(Y 不包含于 X,且 X 并 Y 不等于全属性集),都有 X 是超键,则 R 属于 4NF。

样例:课程信息(课程号, 教师, 参考书),一门课有多位教师和多本参考书,两者互不相干。

  • 存在 课程号 ->-> 教师、课程号 ->-> 参考书;
  • 候选键是 (课程号, 教师, 参考书),全主属性 → 已达 BCNF;
  • 但 课程号 不是超键却决定了多值 → 不满足 4NF。

分解为:开课(课程号, 教师) 与 用书(课程号, 参考书)。

更多通俗样例:

假设关系模式 项目(项目编号, 开发人员, 测试人员):

  • 一个项目有多个开发和多个测试人员,且开发人选和测试人选互不影响。
  • 存在 项目编号 ->-> 开发人员、项目编号 ->-> 测试人员。
  • 候选键 = (项目编号, 开发人员, 测试人员),全主属性 → 达 BCNF。
  • 但 项目编号 不是超键却决定多值 → 不满足 4NF。
  • 分解为:项目开发(项目编号, 开发人员) 和 项目测试(项目编号, 测试人员)。

小结:

  • 只考虑函数依赖时,最高到 BCNF;
  • 考虑多值依赖时,可到 4NF;进一步还有处理连接依赖的 5NF(投影-连接范式)。因此"关系模式最高规范化程度是 4NF"的说法并不准确。

六、模式分解

规范化的手段是模式分解:把一个大关系拆成多个小关系,剥离部分依赖与传递依赖。评价分解好坏看两条标准:

1. 无损连接分解(Lossless Join)

分解后的表自然连接能精确还原原表(不丢信息、不产生假元组)。

通俗理解:无损分解就像把一本相册按主题分成两本,然后把两本放在一起对照,能完美还原原来的相册——不会多出一张照片,也不会少一张。

二元分解判定定理:ρ = {R1, R2} 是无损分解 当且仅当

(R1 交 R2) -> (R1 - R2) 或 (R1 交 R2) -> (R2 - R1)

在 F+ 中成立(公共属性能函数决定某一侧的私有属性)。

样例:学生(学号, 系名, 系主任) 拆成 学生(学号, 系名) 和 系(系名, 系主任),公共属性 系名 满足 系名 -> 系主任,故为无损分解。

更多通俗样例:

假设 R(A, B, C),F = {A -> B},分解为 R1(A, B) 和 R2(B, C):

  • R1 交 R2 = {B},R1 - R2 = {A},R2 - R1 =
  • 检查:B -> A?不成立。B -> C?不成立。
  • 结论:有损分解!自然连接后会产生假元组。

假设 R(A, B, C),F = {A -> B},分解为 R1(A, B) 和 R2(A, C):

  • R1 交 R2 = {A},R1 - R2 = {B},R2 - R1 =
  • 检查:A -> B?成立!
  • 结论:无损分解!

多于两个子模式时用 Chase(追踪)算法 / 矩阵法 判定。

2. 保持函数依赖(Dependency Preserving)

把 F 投影到各子模式上得到 Gi,若 Gi 的并集与 F 等价(能覆盖 F 中每个依赖),则分解保持依赖。

通俗理解:保持依赖就是"拆表后,原来所有的规则(依赖)仍然能在某一张子表里被检查到"。如果拆完后某条规则分散在两张表里,没法单独检查,那就是丢失了依赖。

充分条件:若 F 中每个依赖都完整地落在某个子模式中,则一定保持依赖;反之不成立,需用闭包进一步检验。

3. 两者的取舍

分解目标 无损连接 保持依赖 说明
分解到 3NF 总能做到 总能做到 有合成算法保证
分解到 BCNF 一定能 未必能 有时为了消除主属性的传递依赖,不得不牺牲依赖保持

通俗理解:3NF 是"既要又要"的折中方案——既能无损还原,又能保持所有规则;BCNF 更彻底地消除了冗余,但偶尔会"拆得太碎"导致某些规则没法在单表中检查。

工程实践中常选 3NF 作为平衡点;若对冗余零容忍,则选 BCNF 并在应用层补偿丢失的依赖检查。


七、事务与并发控制

1. 事务的 ACID 特性

特性 含义 通俗样例
原子性 Atomicity 操作要么全做,要么全不做 转账就像"过独木桥"——要么成功过去,要么原地不动,不可能卡在中间
一致性 Consistency 事务执行前后数据库都处于合法状态 转账前后两个账户的总金额不变,不会凭空多出或消失钱
隔离性 Isolation 并发事务互不干扰,中间状态对外不可见 你在转账过程中,别人看不到你"只扣了A的钱还没加到B"的中间状态
持久性 Durability 一旦提交,修改永久保存(即使宕机) 转账显示"成功"后就算立刻断电,钱也已经安全转过去了

注:第四个性质的标准译名是持久性(Durability),不宜写作"持续性"或"改变持续性"。

基本操作:COMMIT(提交,相当于"确认执行")、ROLLBACK(回滚,相当于"撤销重来")。

ACID 综合样例(银行转账):

场景:A 账户转 1000 元到 B 账户。

  1. 原子性:这笔转账包含两个操作——A 扣 1000、B 加 1000。这两个操作必须作为一个整体:要么都成功,要么都失败。不可能出现 A 扣了但 B 没加上。
  2. 一致性:转账前 A+B 的总额是 X,转账后 A-1000 + B+1000 的总额仍然是 X。数据库的总额约束始终成立。
  3. 隔离性:如果在转账过程中有人查看 A 的余额,他要么看到转账前的值,要么看到转账后的值,不会看到一个"已经扣了但还没加上"的中间值。
  4. 持久性:转账成功后(COMMIT),即使数据库立刻崩溃重启,这笔交易也不会消失。

2. 并发带来的三类问题

问题 描述 通俗样例
丢失修改(丢失更新) 两个事务同读同写,后写回的覆盖了先写回的 两个人同时改同一份文档的同一处,后保存的人把先保存的人的修改覆盖了
读脏数据 读到别的事务尚未提交(后来还被回滚)的数据 A 改了余额但还没保存,B 看到了这个未保存的值,然后 A 取消了修改——B 看到的就是"脏"数据
不可重复读 同一事务内两次读取同一数据,结果不一致 你第一次看银行账户是 1000 元,期间别人转了一笔钱进来,你再一看变成 1500 元了

(另有"幻读":同一查询条件两次查出的行数不同,通常由插入/删除引起。比如你查"工资大于 5000 的员工",第一次查到 10 个人,期间有人升职工资超过 5000 了,你再查就变成 11 个人了——好像"幻化"出一个人一样。)

并发问题综合样例:

假设有两个事务 T1 和 T2 同时操作库存表中的商品 A:

时间线 事务 T1 事务 T2 问题
T1 读取库存 = 100
T2 读取库存 = 100
T1 库存 = 100 - 10 = 90,写回
T2 库存 = 100 - 20 = 80,写回 丢失更新:T1 的 -10 被覆盖了,实际应该减 30 却只减了 20
T1 读取库存 = 100
T2 库存 = 100 + 50 = 150,写回(未提交)
T1 读到库存 = 150 读脏数据:T2 后来回滚了,T1 读到的 150 是无效数据
T1 读取库存 = 100
T2 库存 = 100 + 50 = 150,写回并提交
T1 再次读取库存 = 150 不可重复读:同一次事务中两次读,结果不同

3. 锁的类型

  • X 锁(排他锁 / 写锁):加锁事务可读可写,其他事务不能加任何锁——就像你给文件加了"只读"以外的锁,别人既不能读也不能改,等你改完释放锁才行。
  • S 锁(共享锁 / 读锁):加锁事务只能读,其他事务只能再加 S 锁(可读不可写)——就像文件的"只读"属性,多人可以同时看,但没人能改。

通俗理解:

  • X 锁 = 独占模式:你在会议室开会(写数据),别人不能进(不能读也不能写)。
  • S 锁 = 共享模式:大家在图书馆看书(读数据),多人可以同时看,但想改内容(写数据)不行,得等所有人都看完。

4. 三级封锁协议

协议 规定 解决的问题 通俗理解
一级 写数据前加 X 锁,直到事务结束才释放 丢失修改 要改东西先独占,改完才放人
二级 一级 + 读前加 S 锁,读完即可释放 丢失修改、读脏数据 看完就放手,不会一直占着不让别人改
三级 一级 + 读前加 S 锁,直到事务结束才释放 丢失修改、读脏数据、不可重复读 看完也不放手,直到你事务结束,保证你不会再读到老数据

规律:读锁握得越久,一致性越强,并发性越差。 就像上厕所——门链子挂得越久(锁释放越晚),别人越等不到位置(并发度低),但你的隐私和体验越好(一致性高)。

5. 两段锁协议(2PL)

事务执行分为两个阶段:

  • 增长阶段(加锁阶段):只能申请锁(读申请 S,写申请 X),申请不到就等待,不得解锁——就像进货阶段,只能往仓库里放东西,不能往外拿。
  • 收缩阶段(解锁阶段):只能释放锁,不得再加锁——就像出货阶段,只能往外拿东西,不能再往仓库里放。

实务做法:从第一条语句起一直加锁,到执行 COMMIT / ROLLBACK 时一次性进入解锁阶段释放全部锁。

遵守 2PL 的事务调度一定是可串行化的;但 2PL 不能避免死锁(双方互相等待对方的锁)。

死锁通俗样例:

两个人去吃饭,桌上只有一双筷子。A 拿到了左筷子,B 拿到了右筷子。A 等 B 放下右筷子,B 等 A 放下左筷子——谁也吃不了,死锁了。

6. 死锁的处理

  • 预防:一次封锁法(一次性申请全部锁,要么全拿到要么全不拿,避免部分持有)、顺序封锁法(约定全局加锁顺序,比如永远先锁 A 再锁 B,避免环路)。
  • 检测与解除:构造等待图(有环即死锁)或设置超时;解除时通常撤销代价最小(已做工作量最少)的事务,让其回滚并释放锁。

通俗理解:

  • 一次封锁法 = "贪心法":做事之前先把所有需要的资源全部拿到手,拿不齐就不开始。不会出现"拿到一半等另一半"的情况。
  • 顺序封锁法 = "排队法":约定所有人按固定顺序拿资源,比如永远先拿筷子再拿碗。不会出现 A 等 B、B 等 A 的循环等待。
  • 超时法 = "等太久就放弃":等锁超过一定时间还没拿到,就主动放弃回滚,过一会儿再试。

八、一张速查总表

概念 一句话要点 通俗记忆法
函数依赖 X 定则 Y 必定的语义约束 " Knowing X means you know Y"
部分/传递依赖 真子集能定 / 经第三者中转且不等价 "部分"=只用一部分就能定;"传递"=绕路通过第三者
Armstrong 公理 自反、增广、传递 → 合并、分解、伪传递 三条公理是"根",三条推论是"枝叶"
候选键 极小超键;用闭包法最稳妥 "最小的能唯一标识的组合"
外键 本表属性,参照他表(或自身)候选键,可为空 "本表的引用列,指向别人的主键"
1NF 属性不可再分,不能有"小表" "一格只能填一个值"
2NF 砍掉非主属性的部分依赖 "非主属性必须跟整个主键有关"
3NF 再砍掉非主属性的传递依赖 "非主属性不能通过其他非主属性间接挂钩"
BCNF 每个决定因素都是超键(主属性也不能例外) "谁能决定别人?只有候选键才有这个权力"
4NF 再砍掉非平凡多值依赖 "一对多且互相独立的属性要拆开"
无损分解 公共属性能函数决定某一侧私有属性 "拆开后能完美拼回去"
保持依赖 所有依赖都能在某张子表里被检验 "拆表后规则没丢"
ACID 原子、一致、隔离、持久 "要么全做要么全不做,做完就永久保存"
三级封锁 一级防丢失修改,三级再加不可重复读 "锁得越久越安全但也越慢"
两段锁 保可串行化,但不保不死锁 "先加锁后解锁,两段不能混"

整理完毕。若需配套习题(如求候选键、判断范式级别、Chase 算法演练、调度可串行化判定),可在此基础上继续扩展。

posted @ 2026-10-28 16:50  渡solong~  阅读(7)  评论(0)    收藏  举报