整个数据库完整性的知识体系,可以看作是解决一个核心问题:如何确保数据库里的数据是正确的、符合逻辑的? 为了解决这个问题,数据库系统提供了一套由简到繁、由内到外的“防御系统”。
一、数据库完整性概述
1.1 什么是数据库完整性?
- 数据的正确性:数据符合现实世界语义,反映实际状况。
- 数据的相容性:同一对象在不同表中的数据逻辑一致。
1.2 完整性 vs 安全性
1.3 完整性机制
- 定义完整性约束
- 使用 SQL 的 DDL 语句定义
- 包括:实体完整性、参照完整性、用户定义完整性
- 完整性检查
- 在执行
INSERT、UPDATE、DELETE 后或事务提交时检查
- 违约处理
- 拒绝执行(NO ACTION)
- 级联操作(CASCADE)
- 设置为空值(SET-NULL)
二、实体完整性
2.1 定义
- 使用
PRIMARY KEY 定义主码 - 主码唯一且非空
2.2 定义方式
- 列级定义:
1
2
3
4
| CREATE TABLE Student (
Sno CHAR(8) PRIMARY KEY,
...
);
|
- 表级定义(适用于多属性主码):
1
2
3
4
5
| CREATE TABLE SC (
Sno CHAR(8),
Cno CHAR(5),
PRIMARY KEY (Sno, Cno)
);
|
2.3 检查与违约处理
- 检查主码是否唯一、非空
- 使用 B+ 树索引避免全表扫描
- 违约则拒绝操作
三、参照完整性
3.1 定义
- 使用
FOREIGN KEY 和 REFERENCES 定义外码 - 外码引用其他表的主码
3.2 示例
1
2
3
4
5
6
| CREATE TABLE SC (
Sno CHAR(8),
Cno CHAR(5),
FOREIGN KEY (Sno) REFERENCES Student(Sno),
FOREIGN KEY (Cno) REFERENCES Course(Cno)
);
|
3.3 违约处理
| 操作 | 说明 | | ——— | —————- | | NO ACTION | 拒绝执行(默认) | | CASCADE | 级联删除/更新 | | SET-NULL | 将外码设为空值 |
3.4 显式定义违约处理
1
2
3
| FOREIGN KEY (Sno) REFERENCES Student(Sno)
ON DELETE CASCADE
ON UPDATE CASCADE;
|
四、用户定义完整性
4.1 属性级约束
- 非空:
NOT NULL - 唯一:
UNIQUE - 条件检查:
CHECK
示例:
1
2
3
4
5
6
| CREATE TABLE Student (
Sno CHAR(8) PRIMARY KEY,
Sname CHAR(20) NOT NULL,
Ssex CHAR(6) CHECK (Ssex IN ('男', '女')),
Grade SMALLINT CHECK (Grade BETWEEN 0 AND 100)
);
|
4.2 元组级约束
示例:
1
2
3
4
| CREATE TABLE Student (
...
CHECK (Ssex = '女' OR Sname NOT LIKE 'Ms.%')
);
|
4.3 完整性约束命名与修改
- 使用
CONSTRAINT 命名 - 使用
ALTER TABLE 修改
示例:
1
2
3
4
5
6
| -- 添加约束
ALTER TABLE Student
ADD CONSTRAINT C1 CHECK (Sno BETWEEN '900000' AND '999999');
-- 删除约束
ALTER TABLE Student DROP CONSTRAINT C1;
|
五、域与断言
5.1 域(Domain)
示例:
1
2
3
4
5
| CREATE DOMAIN GenderDomain CHAR(6)
CHECK (VALUE IN ('男', '女'));
-- 使用域
Ssex GenderDomain
|
5.2 断言(Assertion)
示例:
1
2
3
4
5
6
| -- 限制“数据库”课程最多60人选修
CREATE ASSERTION ASSE_SC_DB_NUM
CHECK (60 >= (
SELECT COUNT(*) FROM SC, Course
WHERE SC.Cno = Course.Cno AND Course.Cname = '数据库'
));
|
六、触发器
6.1 触发器概述
- 是一种“事件-条件-动作”规则
- 由
INSERT、UPDATE、DELETE 激活 - 可在操作前(
BEFORE)或后(AFTER)执行
6.2 触发器类型
- 行级触发器:每行触发一次,可使用
NEW/OLD - 语句级触发器:每语句触发一次,不能使用
NEW/OLD
6.3 示例
例1:记录分数修改
1
2
3
4
5
6
7
8
| CREATE TRIGGER SC_T
AFTER UPDATE OF Grade ON SC
REFERENCING
OLD AS OldTuple,
NEW AS NewTuple
FOR EACH ROW
WHEN (NewTuple.Grade >= 1.1 * OldTuple.Grade)
INSERT INTO SC_U VALUES (OldTuple.Sno, OldTuple.Cno, OldTuple.Grade, NewTuple.Grade);
|
例2:统计插入学生数
1
2
3
4
5
| CREATE TRIGGER Student_Count
AFTER INSERT ON Student
REFERENCING NEW TABLE AS Delta
FOR EACH STATEMENT
INSERT INTO StudentInsertLog SELECT COUNT(*) FROM Delta;
|
例3:自动调整教授工资
1
2
3
4
5
6
7
8
9
| CREATE TRIGGER Update_Sal
BEFORE UPDATE ON Teacher
REFERENCING NEW AS newTuple
FOR EACH ROW
BEGIN
IF newTuple.Job = '教授' AND newTuple.Sal < 4000 THEN
SET newTuple.Sal = 4000;
END IF;
END;
|
6.4 触发器执行顺序
- 执行
BEFORE 触发器 - 执行触发 SQL 语句
- 执行
AFTER 触发器
6.5 删除触发器
1
| DROP TRIGGER Update_Sal ON Teacher;
|
总结回顾
| 模块 | 核心机制 | 关键字/语句 |
|---|
| 实体完整性 | 主码唯一非空 | PRIMARY KEY |
| 参照完整性 | 外码引用 | FOREIGN KEY、REFERENCES |
| 用户定义完整性 | 属性/元组级约束 | CHECK、NOT NULL、UNIQUE |
| 域与断言 | 类型约束、跨表约束 | CREATE DOMAIN、CREATE ASSERTION |
| 触发器 | 事件驱动、复杂逻辑 | CREATE TRIGGER、NEW/OLD |