第五章 数据库完整性
5.1 数据库完整性概述
1. 数据库完整性的定义
数据库的完整性 是指数据库中存储的数据在任何时刻都必须是正确 、有效 和相容的。
- 数据的正确性 :指数据必须符合现实世界的语义,准确反映当前实际状况。
- 示例 :
性别属性的域只能是 '男' 或 '女';成绩属性的域必须在 [0, 100] 区间内;学号属性必须唯一。
- 示例 :
- 数据的相容性 :指数据库中同一对象在不同关系表中的数据必须符合逻辑,保持一致。
- 示例 :学生所选课程的
课程号必须是课程表中已存在的课程号;学生所属院系的院系号必须是院系表中已存在的院系号。
- 示例 :学生所选课程的
2. 完整性与安全性的辨析
完整性和安全性是两个截然不同的概念,必须严格区分:
| 维度 | 数据完整性 | 数据安全性 |
|---|---|---|
| 目标 | 防止数据库中存在不符合语义的、不正确的数据。 | 保护数据库,防止恶意的破坏和非法的存取。 |
| 防范对象 | 不合语义的、不正确的数据。 | 非法用户和非法操作。 |
| 核心机制 | 阻止合法用户 通过合法操作 向数据库中加入不正确的数据。 | 防范非法用户 和非法操作 存取数据库中的正确数据。 |
3. 完整性控制机制
为维护数据库完整性,数据库管理系统 (DBMS) 必须提供一套完整的机制:
- 完整性约束定义:提供定义完整性约束条件(或称完整性规则)的机制。SQL 标准支持通过数据定义语言 (DDL) 来实现。
- **完整性检查:**提供在
INSERT、UPDATE、DELETE等数据操纵操作执行后,或在事务提交时,自动检查数据是否违背完整性约束的机制。 - 违约处理:当系统发现用户的操作违背了完整性约束时,必须采取预定义的动作(如拒绝执行、级联操作)来保证数据的完整性。
DBMS 实现完整性控制的优势在于:
- 减轻了应用程序员的负担,约束条件无需在应用程序中逐一实现。
- 提供了全局一致的完整性保护,避免了因应用逻辑疏忽导致的漏洞。
5.2 实体完整性
1. 实体完整性的定义
关系模型的实体完整性规则要求:基本关系的主码属性值不能为空 (NULL),且必须唯一。
SQL 实现 :在 CREATE TABLE 语句中通过 PRIMARY KEY 关键字定义。
-
单属性主码:可在列级或表级定义。
-
[例 5.1]
Student表的Sno为主码-
(1) 列级约束:
sqlCREATE TABLE Student ( Sno CHAR(8) PRIMARY KEY, Sname CHAR(20) NOT NULL, Ssex CHAR(6), Sbirthdate Date, Smajor VARCHAR(40) ); -
(2) 表级约束:
sqlCREATE TABLE Student ( Sno CHAR(8), Sname CHAR(20) NOT NULL, Ssex CHAR(6), Sbirthdate Date, Smajor VARCHAR(40), PRIMARY KEY (Sno) );
-
-
-
多属性(组合)主码 :必须在表级定义。
-
[例 5.2]
SC表的(Sno, Cno)为主码sqlCREATE TABLE SC ( Sno CHAR(8) NOT NULL, Cno CHAR(5) NOT NULL, Grade SMALLINT, Semester CHAR(5), Teachingclass CHAR(8), PRIMARY KEY (Sno, Cno) /* 只能在表级定义 */ );注:组合主码的各属性必须显式或隐式地声明为
NOT NULL。
-
2. 实体完整性检查与违约处理
- 检查时机 :在
INSERT操作或对主码列执行UPDATE操作时。 - 检查内容 :
- 非空检查 :检查主码的各个属性是否为
NULL。若任一属性为空,操作被拒绝。 - 唯一性检查:检查主码值是否与表中已有元组的主码值重复。若重复,操作被拒绝。
- 非空检查 :检查主码的各个属性是否为
- 违约处理 :拒绝 (REJECT) 执行该数据操纵操作。
- 检查实现 :为避免全表扫描(效率极低),RDBMS 通常会在主码上自动创建一个 UNIQUE 索引 (如 B+ 树索引),以高效地完成唯一性检查。

5.3 参照完整性
1. 参照完整性的定义
- 关系模型的参照完整性规则定义了关系与关系之间的联系 。规则要求:
- 若属性(或属性组)FFF 是基本关系 RRR(称为参照关系 或从表 )的外码 ,它与基本关系 SSS(称为被参照关系 或主表 )的主码 KKK 相对应,则对于 RRR 中每个元组,其 FFF 上的值必须满足以下条件之一:
- 取空值 (NULL)(前提是 F 的所有属性均允许为空)。
- 等于 S 中某个元组的主码 K 的值(即该外码值必须在被参照表中存在)。
- 若属性(或属性组)FFF 是基本关系 RRR(称为参照关系 或从表 )的外码 ,它与基本关系 SSS(称为被参照关系 或主表 )的主码 KKK 相对应,则对于 RRR 中每个元组,其 FFF 上的值必须满足以下条件之一:
SQL 实现 :在 CREATE TABLE 语句中通过 FOREIGN KEY 和 REFERENCES 短语定义。
-
[例]
Student表的Smajor参照MAJOR表的Majorno:sqlCREATE TABLE Student ( Sno CHAR(8) PRIMARY KEY, ... Smajor VARCHAR(40), /* 表级定义参照完整性 */ FOREIGN KEY (Smajor) REFERENCES MAJOR(Majorno) ); -
[例 5.3]
SC表的Sno参照Student,Cno参照Course:sqlCREATE TABLE SC ( Sno CHAR(8) NOT NULL, Cno CHAR(5) NOT NULL, ... PRIMARY KEY (Sno, Cno), /* 定义两个外码约束 */ FOREIGN KEY (Sno) REFERENCES Student(Sno), FOREIGN KEY (Cno) REFERENCES Course(Cno) );
2. 参照完整性检查与违约处理
当对参照表或被参照表执行 INSERT、UPDATE、DELETE 操作时,均可能破坏参照完整性,系统必须进行检查。
可能破坏完整性的情况:
- 参照表 (SC)
INSERT/UPDATE:导致外码值 (Sno) 在被参照表 (Student) 中不存在。 - 被参照表 (Student)
DELETE:导致参照表 (SC) 中仍存在参照该主码的外码。 - 被参照表 (Student)
UPDATE(主码):导致参照表 (SC) 中仍存在参照旧主码的外码。
违约处理策略 :可在定义外码时显式声明 ON DELETE 和 ON UPDATE 的动作。
- 拒绝执行
- 不允许执行该操作。这是 SQL 标准中的默认策略。
- 级联操作
ON DELETE CASCADE:删除被参照元组时,级联删除所有参照该元组的元组。ON UPDATE CASCADE:更新被参照元组的主码时,级联更新所有参照该元组的外码值。
- 设置为空值
ON DELETE SET-NULL:删除被参照元组时,将所有参照该元组的外码值设置为NULL。ON UPDATE SET-NULL:更新被参照元组的主码时,将所有参照该元组的外码值设置为NULL。
重要约束 : 如果外码是参照表主码的组成部分(如 SC 表中的 Sno 和 Cno),则根据实体完整性,这些属性不允许为空 。因此,针对它们的外码约束不能选择 SET-NULL 策略。
[例 5.4] 显式声明违约处理策略:
sql
CREATE TABLE SC (
Sno CHAR(8) NOT NULL,
Cno CHAR(5) NOT NULL,
...
PRIMARY KEY (Sno, Cno),
FOREIGN KEY (Sno) REFERENCES Student(Sno)
ON DELETE CASCADE /* 删除学生时,级联删除其选课记录 */
ON UPDATE CASCADE, /* 更新学号时,级联更新其选课记录 */
FOREIGN KEY (Cno) REFERENCES Course(Cno)
ON DELETE NO ACTION /* 删除课程时,若有学生选课则拒绝 */
ON UPDATE CASCADE /* 更新课程号时,级联更新SC表 */
);
5.4 用户定义的完整性
指针对特定应用领域的需求,由用户自行定义的数据语义约束。
1. 属性上的约束条件
定义在单个属性列上的约束,在 CREATE TABLE 时定义。
-
列值非空
sqlCREATE TABLE SC ( Sno CHAR(8) NOT NULL, Cno CHAR(5) NOT NULL, Grade SMALLINT NOT NULL, Semester CHAR(5), TeachingclassCHAR(8), PRIMARY KEY (Sno, cno), ... -
列值唯一
sqlCREATE TABLE School ( Schoolno CHAR(8), SchooIname VARCHAR(40) UNIQUE NOT NULL, /*要求Schoolname列值唯一,并且不能取空值*/ PRIMARY KEY (Schoolno) ); -
CHECK短语:指定列值必须满足的布尔表达式。-
[例 5.7]
Ssex CHAR(6) CHECK (Ssex IN ('男','女'))
-
[例 5.8]
Grade SMALLINT CHECK (Grade >= 0 AND Grade <= 100)
-
违约处理 :当插入或修改数据不满足上述约束时,操作被拒绝执行。
2. 元组上的约束条件
定义涉及同一元组中多个属性之间相互关系的约束。通常在表级使用 CHECK 短语定义。
-
[例 5.9] 当学生性别为男时,其名字 (
Sname) 不能以 'Ms.' 开头。sqlCREATE TABLE Student ( ... Sname CHAR(20) NOT NULL, Ssex CHAR(6), ... PRIMARY KEY (Sno), /* 定义元组级CHECK约束 */ CHECK (Ssex='女' OR Sname NOT LIKE 'Ms.%') );- 逻辑分析 :若
Ssex='女',约束为真。若Ssex='男',则必须满足Sname NOT LIKE 'Ms.%'约束才为真。
- 逻辑分析 :若
违约处理 :当插入或修改元组导致 CHECK 约束的谓词计算结果为 FALSE 时,操作被拒绝执行。
5.5 完整性约束命名字句
为了便于后续对完整性约束进行管理(如修改或删除),可以使用 CONSTRAINT 子句为约束命名。
1. 约束命名语法
sql
CONSTRAINT <完整性约束条件名> <完整性约束条件>
-
为
Student表的多个约束命名:sql/*建立学生登记表Student,要求学号在10000000~29999999之间,姓名不能取空值,性别只能是 "男" 或 "女" ,出生日期在1980之后,学号是主码。*/ CREATE TABLE Student ( Sno CHAR(8) CONSTRAINT C1 CHECK (Sno BETWEEN '10000000' AND '29999999'), Sname CHAR(20) CONSTRAINT C2 NOT NULL, Ssex CHAR(6) CONSTRAINT C3 CHECK (Ssex IN ('男','女')), Sbirthdate Date CONSTRAINT C4 CHECK (Sbirthdate > '1980-1-1'), Smajor VARCHAR(40), CONSTRAINT StudentKey PRIMARY KEY(Sno) ); -
为
TEACHER表的元组约束命名:sql/*建立教师表TEACHER,要求每个教师的应发工资不低于3000元。应发工资是工资列Sal与扣除项Deduct之和。*/ CREATE TABLE TEACHER ( Eno CHAR(8) PRIMARY KEY,/*在列级定义主码*/ Ename VARCHAR(20), JobCHAR(8), Sal NUMERIC(7,2), Deduct NUMERIC(7,2), Schoolno CHAR(8), CONSTRAINT TEACHERFKey FOREIGN KEY (Schoolno) REFERENCES School(Schoolno), CONSTRAINT C1 CHECK (Sal+ Deduct >= 3000) );
2. 修改表中的完整性限制
使用 ALTER TABLE 语句来删除或增加约束。
- 删除约束 :
ALTER TABLE Student DROP CONSTRAINT C3;
- 增加约束 :
ALTER TABLE Student ADD CONSTRAINT <约束名> <约束定义>;
- 修改约束 :
- SQL 标准不支持 直接修改约束。通常通过先删除旧约束 ,再添加新约束 来实现。

- SQL 标准不支持 直接修改约束。通常通过先删除旧约束 ,再添加新约束 来实现。
5.7 触发器
-
触发器 是一种由事件驱动的特殊过程,定义在关系表上。当特定的数据操纵事件(
INSERT,UPDATE,DELETE)发生时,触发器被自动激活,执行预定义的动作。 -
触发器提供了比
CHECK约束更复杂、更精细的完整性控制能力,它遵循 ECA 规则。
1. 定义触发器
语法结构:
sql
CREATE TRIGGER <触发器名>
{ BEFORE | AFTER } <触发事件> ON <表名>
[ REFERENCING ( NEW | OLD ) ( ROW | TABLE ) AS <变量名> ]
[ FOR EACH { ROW | STATEMENT } ]
[ WHEN ( <触发条件> ) ]<触发动作体>
- 触发时机 (
BEFORE | AFTER) :BEFORE(动作执行前)或AFTER(动作执行后)。 - 触发事件 (
INSERT | UPDATE | DELETE):激活触发器的 DML 操作。 - 触发对象 (
ON <表名>):触发器必须定义在基本表上。 - 触发类型 (
ROW | STATEMENT) :- 语句级 (
FOR EACH STATEMENT):操作执行一次,触发器激活一次(无论影响多少行)。 - 行级 (
FOR EACH ROW):操作影响几行,触发器激活几次。
- 语句级 (
- 参照变量 (
REFERENCING) :OLD ROW AS OldTuple:引用行级操作前的旧值。NEW ROW AS NewTuple:引用行级操作后的新值。NEW TABLE AS DELTA:引用语句级INSERT后的新值集合。
- 触发条件 (
WHEN):可选。只有当该条件为真时,触发动作体才执行。 - 触发动作体 (
Trigger Body):一个 SQL 过程块(如 PL/SQL)。
示例:
-
[例 5.21] 行级
AFTER触发器 :当SC表的Grade更新且增幅超过 10% 时,记录日志。sqlCREATE TRIGGER SC_T AFTER UPDATE OF Grade ON SC REFERENCING OLD row AS OldTuple, NEW row AS NewTuple FOR EACH ROW WHEN (NewTuple.Grade >= 1.1 * OldTuple.Grade) BEGIN INSERT INTO SC_U(Sno, Cno, OldGrade, NewGrade) VALUES(OldTuple.Sno, OldTuple.Cno, OldTuple.Grade, NewTuple.Grade); END; -
[例 5.23] 行级
BEFORE触发器 :实现CHECK无法完成的违约处理(自动修改)。教授工资若低于 4000,自动设为 4000。sqlCREATE TRIGGER Insert_Or_Update_Sal BEFORE INSERT OR UPDATE ON Teacher REFERENCING NEW row AS newtuple FOR EACH ROW BEGIN IF (newtuple.Job = '教授') AND (newtuple.Sal < 4000) THEN newtuple.Sal := 4000; END IF; END; -
语句级
AFTER触发器 :统计Student表每次插入的学生人数。sqlCREATE TRIGGER Student_Count AFTER INSERT ON Student REFERENCING NEW TABLE AS DELTA FOR EACH STATEMENT INSERT INTO StudentInsertLog (Numbers) SELECT COUNT(*) FROM DELTA;
2. 激活触发器
触发器由数据库服务器在响应触发事件时自动执行。若一个表上定义了多个触发器,执行遵循以下顺序:
- 执行所有
BEFORE触发器。 - 执行激活触发器的 SQL 语句 (DML)。
- 执行所有
AFTER触发器。
3. 删除触发器
sql
DROP TRIGGER <触发器名> ON <表名>;
Student_Count
AFTER INSERT ON Student
REFERENCING NEW TABLE AS DELTA
FOR EACH STATEMENT
INSERT INTO StudentInsertLog (Numbers)
SELECT COUNT(*) FROM DELTA;
2. 激活触发器
触发器由数据库服务器在响应触发事件时自动执行。若一个表上定义了多个触发器,执行遵循以下顺序:
- 执行所有
BEFORE触发器。 - 执行激活触发器的 SQL 语句 (DML)。
- 执行所有
AFTER触发器。
3. 删除触发器
sql
DROP TRIGGER <触发器名> ON <表名>;