数据库完整性
一、核心概念
(一)数据库完整性定义
- 目标:确保数据的正确性(符合语义约束)和相容性(数据间逻辑一致),防止无效数据输入输出(如 “垃圾进垃圾出” 问题)。
- 与安全性区别:
- 完整性:关注数据本身的语义合法性,防止错误数据。
- 安全性:关注数据的访问控制,防止恶意破坏和非法存取。
(二)完整性约束条件
-
作用对象:列(属性)、元组(记录)、关系(表)。
-
分类:
类型 对象 说明 静态列级约束 列 限制列的取值范围(如数据类型、格式、值域、非空等)。 静态元组约束 元组 元组内各列之间的逻辑关系(如年龄 > 0 且工资 > 年龄 ×100)。 静态关系约束 关系 表间或表内元组的关联约束(如实体完整性、参照完整性、函数依赖)。 动态列级约束 列 修改列定义或值时的约束(如修改列类型时需保证数据兼容)。 动态元组约束 元组 元组值修改前后的逻辑关系(如工资调整后不能低于原工资的 90%)。 动态关系约束 关系 表状态变化的约束(如事务的原子性、一致性)。
二、完整性控制机制
(一)DBMS 的核心功能
- 定义功能:通过 DDL 语句(如
CREATE TABLE
)声明完整性约束。 - 检查功能:在数据插入、更新、删除时触发约束检查。
- 处理功能:若违反约束,执行预设动作(如拒绝操作、级联更新、自动修复数据)。
(二)约束检查方式
- 立即执行约束:操作完成后立即检查(默认方式)。
- 延迟执行约束:事务提交前检查(适用于跨表或跨事务的约束)。
(三)完整性规则模型
-
五元组表示
:
(D, O, A, C, P)
D
:约束作用的数据对象(如列、表)。O
:触发约束的操作(如INSERT
、UPDATE
)。A
:约束条件(如 “工资≥1000”)。C
:筛选数据的谓词(如 “职称 =‘教授’”)。P
:违反约束时的处理过程(如拒绝操作、设置默认值)。
示例:
-
约束
:教授工资不得低于 1000 元。
D
:工资列(Sal)O
:UPDATE
操作A
:Sal ≥ 1000C
:职称 =‘教授’P
:拒绝执行更新
三、典型完整性约束实现
(一)实体完整性
-
目标:确保表中每一行数据唯一标识(通过主码实现)。
-
SQL 实现
(Oracle 示例):
CREATE TABLE Student (Sno NUMBER(8) CONSTRAINT PK_SNO PRIMARY KEY, -- 单字段主码Sname VARCHAR(20),Sage NUMBER(20) );CREATE TABLE SC (Sno NUMBER(8),Cno NUMBER(2),Grade NUMBER(2),CONSTRAINT PK_SC PRIMARY KEY(Sno, Cno) -- 联合主码 );
(二)参照完整性
-
目标:维护表间关联数据的一致性(通过外码实现)。
-
关键问题:
- 外码是否允许为空值?
- 被参照表删除 / 更新主码时的级联操作(如
CASCADE
、RESTRICT
、SET NULL
)。
-
SQL 实现
(Oracle 示例):
CREATE TABLE EMP (Empno NUMBER(4),Deptno NUMBER(2),CONSTRAINT FK_DEPTNO FOREIGN KEY(Deptno)REFERENCES DEPT(Deptno) ON DELETE CASCADE -- 级联删除 );
(三)用户定义完整性
-
目标:自定义业务规则(如值域、格式、逻辑运算约束)。
-
SQL 实现:
-
非空约束(NOT NULL):
CREATE TABLE Student (Sname VARCHAR(20) CONSTRAINT C2 NOT NULL -- 姓名非空 );
-
唯一约束(UNIQUE):
CREATE TABLE DEPT (Dname VARCHAR(9) CONSTRAINT U1 UNIQUE -- 部门名称唯一 );
-
检查约束(CHECK):
CREATE TABLE Student (Sno NUMBER(5) CONSTRAINT C1 CHECK(Sno BETWEEN 90000 AND 99999), -- 学号范围Ssex VARCHAR(2) CONSTRAINT C4 CHECK(Ssex IN('男','女')) -- 性别枚举 );
-
基于表达式的约束:
CREATE TABLE EMP (Sal NUMBER(7,2),Deduct NUMBER(7,2),CONSTRAINT C1 CHECK(Sal + Deduct <= 3000) -- 应发工资上限 );
-
四、高级实现:触发器(Trigger)
-
作用:对复杂业务规则(如跨表约束、自动数据修复)进行编程控制。
-
示例
:教授工资低于 4000 元时自动调整为 4000 元(Oracle PL/SQL):
CREATE TRIGGER UPDATE_SAL BEFORE INSERT OR UPDATE OF Sal, Pos ON Teacher FOR EACH ROW WHEN (NEW.Pos = '教授') BEGINIF NEW.Sal < 4000 THENNEW.Sal := 4000; -- 自动修复数据END IF; END;
五、总结
- 核心原则:通过多层约束(列级、元组级、关系级)和动态机制(触发器)确保数据符合业务语义。
- 实践建议:
- 优先使用 DBMS 内置约束(如
PRIMARY KEY
、CHECK
)实现简单规则。 - 复杂规则通过触发器或应用层逻辑实现,避免过度依赖数据库层。
- 结合事务机制(如
COMMIT
/ROLLBACK
)处理延迟约束,保证数据一致性。
- 优先使用 DBMS 内置约束(如
第八章 关系数据理论
一、核心理论与问题引入
(一)关系模式与数据依赖
- 关系模式定义:
形式化表示为R(U, F)
,其中U
是属性集合,F
是函数依赖集合。例如,学生选课关系SC(Sno, Cno, Grade)
中,F = {(Sno, Cno)→Grade}
。 - 数据依赖类型:
- 函数依赖(FD):如
Sno→Sdept
(学号决定所在系)。 - 多值依赖(MVD):后续章节深入,本章以函数依赖为主。
- 函数依赖(FD):如
(二)不良模式的四大问题(以学生 - 系 - 课程表为例)
问题类型 | 具体表现 |
---|---|
数据冗余 | 系主任姓名重复存储(如计算机系的 “张明” 重复出现在所有该系学生记录中)。 |
更新异常 | 修改系主任时需更新所有该系学生记录,漏改会导致数据不一致。 |
插入异常 | 新系无学生时无法插入系信息(主码 (Sno, Cno) 不能为空)。 |
删除异常 | 删除最后一个学生记录时,系信息也被删除(“级联删除” 副作用)。 |
解决思路:通过模式分解将一个 “大而全” 的关系拆分为多个 “小而精” 的关系,如拆分为 Student(Sno, Sdept)
、Dept(Sdept, Mname)
、SC(Sno, Cno, Grade)
。
二、核心概念:码与函数依赖
(一)码的分类与判定
-
候选码:
- 能唯一标识元组且最小化的属性组,如
SC(Sno, Cno)
中的(Sno, Cno)
。
- 能唯一标识元组且最小化的属性组,如
-
判定原则:
- 出现在所有函数依赖左部的属性必为主属性(如
Sno
在Sno→Sdept
左部,是主属性)。 - 不在任何函数依赖中的属性必包含在候选码中(全码场景,如
R(P, W, A)
中无函数依赖,全属性组为码)。 - U中只出现在F中函数依赖的右部的属性,一定不是主属性,不出现在任何码中。
- 出现在所有函数依赖左部的属性必为主属性(如
-
主码:从候选码中选定的一个,如学生表选
Sno
为主码。 -
外码:其他表的主码,如
SC
中的Sno
是Student
表的主码,故为外码。
(二)函数依赖的类型
-
完全函数依赖:
- 记为
X →^F Y
,如(Sno, Cno)→Grade
(单个Sno
或Cno
无法决定Grade
)。
- 记为
-
部分函数依赖
:
- 记为
X →^P Y
,如(Sno, Cno)→Sdept
(仅Sno
即可决定Sdept
)。
- 记为
-
传递函数依赖
:
- 如
Sno→Sdept→Mname
,则Sno→^t Mname
是传递依赖(Sdept
是中间属性)。
- 如
例题:
考虑某商业集团数据库中的关系模式:销售(商店编号,商品编号,库存数量,部门编号,负责人)。若规定:①每个商店的每种商品只在一个部门销售;②每个商店的每个部门只有一个负责人;③每个商店的每种商品只有一个库存数量。
解析:
•根据语义,此关系模式满足的函数依赖为:
① (商店编号,商品编号)→部门编号
② (商店编号,部门编号)→负责人
③ (商店编号,商品编号)→库存数量
•由于商店编号和商品编号只出现在函数依赖的左部,码中必含有这两个属性。且(商店编号,商品编号)能够完全函数决定该关系的所有属性,因此候选码(也是主码)为:(商店编号,商品编号)。
•主属性包括:商店编号和商品编号。
非主属性包括:部门编号、负责人和库存数量。
三、范式分解:从 1NF 到 BCNF
(一)范式层级与判定标准
范式 | 核心要求 | 解决的问题 |
---|---|---|
1NF | 属性不可再分(如 “地址” 不能包含省、市、区多列)。 | 基础格式规范 |
2NF | 消除非主属性对码的部分依赖(如拆 S-L-C(Sno, Cno, Sdept, Sloc, Grade) 为 SC 和 S-L )。 | 部分依赖导致的冗余 |
3NF | 消除非主属性对码的传递依赖(如拆 S-L(Sno, Sdept, Sloc) 为 S-D 和 D-L )。 | 传递依赖导致的冗余 |
BCNF | 消除主属性对码的不良依赖(如 STJ(S, T, J) 中 T→J 需拆分为 ST 和 TJ )。 | 主属性间的依赖异常 |
(二)分解步骤与示例
1. 从非 2NF 到 2NF:消除部分依赖
案例:关系模式 S-L-C(Sno, Cno, Sdept, Sloc, Grade)
-
函数依赖:
(Sno, Cno)→^F Grade
,Sno→^P Sdept
,Sno→^P Sloc
(Sdept→Sloc
)。 -
分解步骤:
- 保留完全依赖:
SC(Sno, Cno, Grade)
。 - 提取部分依赖:
S-L(Sno, Sdept, Sloc)
。
- 保留完全依赖:
-
结果:
SC∈2NF
,S-L∈2NF
(但S-L
仍存在传递依赖,需进一步分解)。
2. 从 2NF 到 3NF:消除传递依赖
案例:关系模式 S-L(Sno, Sdept, Sloc)
- 函数依赖:
Sno→Sdept→Sloc
(Sno→Sloc
是传递依赖)。 - 分解步骤:
- 保留直接依赖:
S-D(Sno, Sdept)
。 - 提取传递依赖:
D-L(Sdept, Sloc)
。
- 保留直接依赖:
- 结果:
S-D∈3NF
,D-L∈3NF
。
3. 从 3NF 到 BCNF:消除主属性依赖
案例:关系模式 STJ(S, T, J)
(学生 - 教师 - 课程,T→J
)
- 函数依赖:
(S, J)→T
,(S, T)→J
,T→J
(T
是决定因素但非码)。 - 分解步骤:
- 提取
T→J
:TJ(T, J)
。 - 保留剩余属性:
ST(S, T)
。
- 提取
- 结果:
TJ∈BCNF
,ST∈BCNF
(所有决定因素均包含码)。
四、寻找码的实用技巧
(一)属性分类法
- L 类属性:只出现在函数依赖左部(如
Sno
、OrderNo
),必为主属性,包含在候选码中。 - R 类属性:只出现在函数依赖右部(如
Grade
、Sloc
),必为非主属性,不包含在候选码中。 - N 类属性:未出现在任何函数依赖中,必包含在候选码中(全码场景)。
- LR 类属性:同时出现在左、右部(如
Sdept
、T
),需进一步判断。
(二)闭包计算法(简化版)
- 步骤:
- 假设候选码为
X
,从 L 类和 N 类属性开始,逐步添加 LR 类属性。 - 计算
X
的闭包X+
,若X+ = U
,则X
是候选码。
- 假设候选码为
- 示例:
- 关系模式
销售(商店编号, 商品编号, 库存数量, 部门编号, 负责人)
- L 类:
商店编号, 商品编号
;R 类:库存数量, 部门编号, 负责人
- 计算
(商店编号, 商品编号)+
:通过函数依赖推导,可决定所有属性,故为候选码。
- 关系模式
五、规范化实战:书店订单案例
题目:
考虑某书店的图书销售关系Book_Order,该关系涉及的属性包括:OrderNo(订单号)、ClientNo(客户号)、ClientName(客户名)、Address(客户地址)、BookNo(书号)、Title(书名)、Publisher(出版社)、Price(单价)、Quantity(订购数量)。
根据现实世界已知的事实,对于上述关系有如下语义:
•订单号、客户号和书号分别是订单、客户和图书的标识。
•每份订单只对应唯一的一个客户,但一个客户可以有多个订单。
•每个客户有唯一的客户名称和地址。
•每种图书有唯一的书名、出版社和单价。
•每份订单可以包含多种图书,每种图书可以在多份订单中订购。
•每份订单订购每一种图书,有唯一的订购数量。
(一)原始关系:Book_Order
-
属性:
OrderNo, ClientNo, ClientName, Address, BookNo, Title, Publisher, Price, Quantity
-
函数依赖:
OrderNo→ClientNo
,ClientNo→ClientName, Address
,BookNo→Title, Publisher, Price
,(OrderNo, BookNo)→Quantity
-
主码:(orderNo,BookNo)
-
问题:非主属性
ClientName, Address, Title
等存在部分依赖和传递依赖。
(二)分解过程
- 第一步:拆分为 3 个关系(消除部分依赖)
OrderClient(OrderNo, ClientNo)
(主码OrderNo
)Client(ClientNo, ClientName, Address)
(主码ClientNo
)OrderBook(OrderNo, BookNo, Quantity)
(主码(OrderNo, BookNo)
)Book(BookNo, Title, Publisher, Price)
(主码BookNo
)
- 验证:
- 所有非主属性完全依赖于码,且无传递依赖,满足 3NF。
- 若需进一步优化(如
Book
中Publisher
是否需要独立成表),可根据业务需求决定是否拆至 BCNF。
六、核心总结:范式选择与平衡
-
优先目标:
一般业务系统分解至 3NF 即可,避免过度分解导致多表连接性能下降。 -
判断口诀
:
- 1NF:属性原子化,拒绝 “表中表”。
- 2NF:码是组合键时,检查非主属性是否部分依赖。
- 3NF:非主属性间不能 “间接决定”(如
A→B→C
需拆)。 - BCNF:主属性也不能 “任性决定”(如
教师→课程
需拆)。
-
终极目标:
消除冗余与异常,同时兼顾查询效率,通过索引优化关联查询(如为外码添加索引)