第5章 数据库完整性
第5章 数据库完整性
5.1 数据库完整性概述
5.1.1 什么是数据库完整性
数据库的完整性,是指数据库数据的正确性和相容性。
这一定义要拆成两层来理解:
1. 数据的正确性
数据必须符合现实世界的语义,并且能够反映当前的实际状况。
也就是说,数据库里的值不能乱填,必须“有意义、说得通、符合业务规则”。
例如:
- 学生的学号必须唯一;
- 百分制课程的成绩范围应在 0~100 之间;
- 学生选修的课程必须是学校已经开设的课程;
- 学生所在院系必须是学校已成立的院系。
这些都属于“正确性”的要求。
2. 数据的相容性
相容性是指:数据库中同一对象在不同关系表中的表示必须一致。
例如:
- 学生表中有学号
20230001的学生; - 选课表中若出现
20230001,它必须真实对应学生表中的某个学生; - 不能学生表里没有这个人,选课表里却出现了这个学号。
这体现的是多个表之间的一致性,也就是“相容性”。
5.1.2 为什么要研究数据库完整性
数据库是面向现实世界建模的,现实世界本身就有大量规则和限制。
如果数据库不对这些规则进行约束,就会出现大量不合理、错误、矛盾的数据。
结果就是:
- 查询结果不可信;
- 统计分析失真;
- 程序逻辑被破坏;
- 后续更新越来越困难;
- 系统变成“垃圾进,垃圾出”。
所以,完整性的本质就是:
让数据库中的数据始终保持“合法、合理、一致”。
5.1.3 关系数据库管理系统要完成哪些工作
为了维护数据库完整性,关系数据库管理系统(RDBMS)必须至少提供三类能力:
1. 提供定义完整性约束的机制
也就是允许数据库设计者把规则写进数据库。
例如:
PRIMARY KEYFOREIGN KEYNOT NULLUNIQUECHECKCONSTRAINTTRIGGER
2. 提供检查完整性约束的方法
当用户执行 INSERT、UPDATE、DELETE 时,系统要自动检查是否违反约束。
3. 提供违反约束时的处理方法
如果操作违反了完整性规则,系统必须采取措施,例如:
- 拒绝执行;
- 级联修改;
- 级联删除;
- 设为空值;
- 触发器补救;
- 记录日志等。
5.1.4 数据完整性与数据库安全性的区别和联系
这两个概念经常放在一起考,但它们不是一回事。
1. 数据完整性关注什么
完整性关注的是:
数据库中是否存在不符合语义、不正确、不一致的数据。
它防范的是“坏数据”。
例如:
- 成绩写成 150;
- 选课记录引用了不存在的学生;
- 主码为空;
- 同一主码出现两条记录。
2. 数据库安全性关注什么
安全性关注的是:
数据库是否会被非法用户非法访问、恶意破坏或越权操作。
它防范的是“坏人”和“非法操作”。
例如:
- 非授权用户读取机密数据;
- 非法修改学生成绩;
- 恶意删除数据库表;
- 越权访问敏感信息。
3. 两者的联系
二者都属于数据库可靠运行的重要保障,都会影响数据库质量。
可以这样理解:
- 完整性解决“数据对不对”的问题;
- 安全性解决“谁能不能碰数据”的问题。
一个面向数据是否合法,一个面向操作是否有权。
5.2 实体完整性
实体完整性是关系模型中最基本、最核心的一类完整性约束。
5.2.1 实体完整性的定义
实体完整性规则可以概括为:
若属性 A 是基本关系 R 的主属性,则属性 A 不能取空值。
更通俗地说:
- 每个元组都必须能够被唯一标识;
- 主码不能为
NULL; - 主码也不能重复。
否则,就无法区分实体,也无法唯一表示一条记录。
5.2.2 为什么实体完整性必要
在关系数据库中,一张表中的每一行表示一个实体实例。
例如:
Student表中的一行表示一个学生;Course表中的一行表示一门课程;SC表中的一行表示某学生的一门选课记录。
如果主码为空,或者主码重复,就会出现以下问题:
- 无法唯一识别某条记录;
- 无法正确更新或删除某条记录;
- 无法建立可靠的表间引用关系;
- 数据库中的实体概念被破坏。
因此,主码唯一且非空是关系模型最基础的要求。
5.2.3 在 SQL 中如何定义实体完整性
在 CREATE TABLE 中,通常用 PRIMARY KEY 来定义实体完整性。
1. 单属性主码:列级约束写法
CREATE TABLE Student
(
Sno CHAR(8) PRIMARY KEY,
Sname CHAR(20) UNIQUE,
Ssex CHAR(6),
Sbirthdate DATE,
Smajor VARCHAR(40)
);
这里 Sno 是主码,系统会自动保证:
Sno不能为NULLSno不能重复
2. 单属性主码:表级约束写法
CREATE TABLE Student
(
Sno CHAR(8),
Sname CHAR(20) UNIQUE,
Ssex CHAR(6),
Sbirthdate DATE,
Smajor VARCHAR(40),
PRIMARY KEY (Sno)
);
3. 多属性主码:只能写成表级约束
CREATE TABLE SC
(
Sno CHAR(8),
Cno CHAR(5),
Grade SMALLINT,
Semester CHAR(5),
Teachingclass CHAR(8),
PRIMARY KEY (Sno, Cno)
);
这里 (Sno, Cno) 组成复合主码。
注意:
Sno单独可以重复;Cno单独也可以重复;- 但
(Sno, Cno)这个组合不能重复; - 并且组合中的每个属性都不能为
NULL。
5.2.4 实体完整性的检查机制
当用户执行以下操作时,系统会自动检查实体完整性:
- 插入新元组;
- 更新主码列;
- 某些系统中,批量导入也会触发检查。
检查主要包括两项:
1. 检查主码值是否唯一
若新插入记录的主码已存在,则拒绝插入或修改。
2. 检查主码属性是否为空
只要主码中的任一属性为空,就拒绝执行操作。
5.2.5 主码唯一性是如何检查的
课件特别强调了主码检查的实现思路,这部分很容易考原理题。
方法一:全表扫描
系统依次扫描表中所有记录,把新主码值与已有记录进行比较。
优点:
- 原理简单;
- 容易理解。
缺点:
- 代价很高;
- 表越大越慢;
- 插入和更新效率很差。
方法二:索引扫描
为了避免全表扫描,RDBMS 通常会在主码上自动建立索引。
这样检查主码是否已存在时,就不必扫描整张表,而是通过索引快速定位。
课件中以 B+ 树索引为例说明:
- 从根结点开始查找;
- 逐层向下定位;
- 如果在叶结点中找到目标主码值,则表示重复;
- 重复时拒绝插入。
例如,新插入记录主码值为 25:
- 从根结点进入;
- 再进入中间结点;
- 最后到叶结点;
- 若叶结点已存在
25,说明该键已出现,不能插入。
索引的优点
- 索引块通常比数据块少;
- 可以高效查找;
- 若索引较小,可长期缓存在内存中。
索引的代价
- 占用额外存储空间;
- 建立索引需要开销;
- 插入、删除、更新时要维护索引。
所以,索引不是“免费”的,但为了保证主码高效检查,通常非常值得。
5.3 参照完整性
参照完整性用于保证表与表之间引用关系的正确性。
它解决的核心问题是:
一个表中引用另一个表的数据时,被引用的数据必须真实存在。
5.3.1 参照完整性的基本概念
在关系数据库中,表之间经常通过**外码(外键)**建立联系。
例如:
Student(Sno, ...)存储学生信息;Course(Cno, ...)存储课程信息;SC(Sno, Cno, Grade, ...)存储选课信息。
这里:
SC.Sno引用Student.SnoSC.Cno引用Course.Cno
因此:
- 选课表中出现的学号,必须能在学生表中找到;
- 选课表中出现的课程号,必须能在课程表中找到。
否则就会出现“悬空引用”或“孤儿记录”。
5.3.2 在 SQL 中如何定义参照完整性
在 CREATE TABLE 中,通常用:
FOREIGN KEY指定哪些列是外码;REFERENCES指定外码参照哪个表的主码。
例如:
CREATE TABLE SC
(
Sno CHAR(8),
Cno CHAR(5),
Grade SMALLINT,
Semester CHAR(5),
Teachingclass CHAR(8),
PRIMARY KEY (Sno, Cno),
FOREIGN KEY (Sno) REFERENCES Student(Sno),
FOREIGN KEY (Cno) REFERENCES Course(Cno)
);
这里的含义是:
SC.Sno必须参照Student表中的Sno;SC.Cno必须参照Course表中的Cno。
5.3.3 参照完整性为什么会被破坏
参照完整性涉及两个表:
- 被参照表:提供主码值的表,例如
Student; - 参照表:保存外码值的表,例如
SC。
只要对这两个表执行增删改,就可能破坏参照完整性。
课件以 Student 和 SC 为例,列出了四种典型情形。
1. 在 SC 表中插入新元组
如果插入的 Sno 在 Student 表中不存在,则违反参照完整性。
2. 修改 SC 表中的外码值
如果把 SC.Sno 改成一个在 Student 表中不存在的学号,也会违反参照完整性。
3. 从 Student 表中删除元组
如果删除了某个学生,而 SC 中还有这个学生的选课记录,那么 SC 就会出现悬空引用。
4. 修改 Student 表中的主码值
如果把 Student.Sno 改了,而 SC 中对应的外码没有同步更新,也会出现不一致。
5.3.4 参照完整性的违约处理策略
当某个操作可能破坏参照完整性时,系统通常有三种基本处理策略。
1. 拒绝执行(NO ACTION / RESTRICT 思想)
这是最常见、最保守的方式。
含义是:
- 不允许执行会破坏引用关系的操作;
- 一旦操作造成不一致,系统直接报错。
例如:
- 学生还有选课记录,就不允许删除该学生。
这种策略通常是默认策略。
2. 级联操作(CASCADE)
当被参照表中的主码被删除或修改时,系统自动对参照表中的对应元组进行同步处理。
例如:
- 删除
Student中某个学生时,自动删除SC中该学生的所有选课记录; - 修改
Student.Sno时,自动更新SC.Sno。
这种方式能保持一致性,但要谨慎使用,因为它可能引发“连锁变化”。
3. 设置为空值(SET NULL)
如果删除或修改被参照表中的元组导致不一致,系统把参照表中的对应外码属性设为 NULL。
这要求外码列允许为空,否则该策略根本无法执行。
例如:
- 删除某位导师时,把学生表中的导师编号设为空。
注意:
- 如果外码本身属于主码的一部分,通常不能设为空;
- 因为主码不能取空值。
5.3.5 显式说明参照完整性的违约处理
SQL 中可以在外键定义后写 ON DELETE、ON UPDATE 子句。
例如:
CREATE TABLE SC
(
Sno CHAR(8),
Cno CHAR(5),
Grade SMALLINT,
Semester CHAR(5),
Teachingclass CHAR(8),
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
);
其含义是:
- 当删除
Student中某学生时,自动级联删除SC中对应选课记录; - 当修改
Student.Sno时,自动级联修改SC.Sno; - 当删除
Course中某课程时,不允许造成SC中引用失效; - 当修改
Course.Cno时,允许级联更新。
5.3.6 参照完整性中的一个关键细节:外码能否为空
课件特别提醒:
定义参照完整性时,除了要定义外码,还要定义外码列是否允许取空值。
因为外码为空的含义通常是:
- 当前记录暂时不参照任何被参照记录;
- 或该联系是可选的,而不是必选的。
例如:
- 学生可以暂时没有导师编号;
- 员工可以暂时没有部门经理编号。
所以,外码是否允许为空,实际上反映了业务规则。
5.4 用户定义的完整性
实体完整性和参照完整性是关系模型中普遍适用的规则。
但在具体应用系统中,还存在大量更细致、更具体的业务语义约束。
这类约束就叫用户定义的完整性。
5.4.1 用户定义的完整性的含义
用户定义的完整性是指:
针对某一具体应用,数据必须满足的语义要求。
例如:
- 学生成绩必须在 0~100 之间;
- 学生姓名不能为空;
- 性别只能取“男”或“女”;
- 教师应发工资不得低于 3000 元;
- 当学生性别为男时,姓名不能以
Ms.开头。
这些都不是关系模型天然规定的,而是某个业务场景自身的要求。
关系数据库系统提供了定义和检查这类约束的机制,因此这部分工作不必完全由应用程序自己承担。
5.4.2 用户定义的完整性分为哪两类
课件把它分为两类:
1. 属性上的约束
约束某一个字段本身的取值。
例如:
- 是否允许为空;
- 是否唯一;
- 是否在规定范围内;
- 是否属于某个枚举集合。
2. 元组上的约束
约束同一行中多个属性之间的关系。
例如:
- 当性别为男时,姓名不能以
Ms.打头; - 工资和扣除项之和必须不低于某个值;
- 开始日期不能晚于结束日期。
5.4.3 属性上的约束
1. 非空约束:NOT NULL
用于规定某列必须有值。
例如,定义 SC 表时要求 Sno、Cno、Grade 不允许为空:
CREATE TABLE SC
(
Sno CHAR(8) NOT NULL,
Cno CHAR(5) NOT NULL,
Grade SMALLINT NOT NULL,
Semester CHAR(5),
Teachingclass CHAR(8),
PRIMARY KEY (Sno, Cno)
);
2. 唯一约束:UNIQUE
用于规定某列的值不能重复。
例如,建立学院表时要求学院名称唯一:
CREATE TABLE School
(
SHno CHAR(8) PRIMARY KEY,
SHname VARCHAR(40) UNIQUE,
SHfounddate DATE
);
这里要注意:
PRIMARY KEY也要求唯一,但主码还要求非空;UNIQUE只强调不重复,不一定天然表示“主标识”。
3. 检查约束:CHECK
用于规定列值必须满足某个条件表达式。
例 1:性别只能是“男”或“女”
CREATE TABLE Student
(
Sno CHAR(8) PRIMARY KEY,
Sname CHAR(20) NOT NULL,
Ssex CHAR(6) CHECK (Ssex IN ('男', '女')),
Sbirthdate DATE,
Smajor VARCHAR(40)
);
例 2:成绩范围必须在 0~100 之间
CREATE TABLE SC
(
Sno CHAR(8),
Cno CHAR(5),
Grade SMALLINT CHECK (Grade >= 0 AND Grade <= 100),
Semester CHAR(5),
Teachingclass CHAR(8),
PRIMARY KEY (Sno, Cno),
FOREIGN KEY (Sno) REFERENCES Student(Sno),
FOREIGN KEY (Cno) REFERENCES Course(Cno)
);
某些数据库系统中,也可以直接使用枚举类型,例如:
Ssex ENUM('男', '女')
但从数据库原理课程角度,最标准、最通用的写法仍然是 CHECK。
5.4.4 属性约束的检查与违约处理
当执行以下操作时,系统会自动检查属性约束:
- 插入元组;
- 更新属性值。
如果约束不满足,系统通常会:
- 拒绝执行该操作;
- 返回错误信息;
- 不允许非法数据写入数据库。
也就是说,属性约束的核心机制是:
先检查,后写入;不合法则拒绝。
5.4.5 元组上的约束
元组级约束解决的是:
同一元组内部,不同属性之间的相互制约关系。
这比单个属性约束更复杂,因为它涉及多个字段之间的逻辑关系。
例如:
CREATE TABLE Student
(
Sno CHAR(8),
Sname CHAR(20) NOT NULL,
Ssex CHAR(6),
Sbirthdate DATE,
Smajor VARCHAR(40),
PRIMARY KEY (Sno),
CHECK (Ssex = '女' OR Sname NOT LIKE 'Ms.%')
);
这个约束的含义是:
- 如果学生性别是女,则条件自动满足;
- 如果学生性别不是女,那么姓名就不能以
Ms.开头; - 从逻辑上等价于:“男生不能叫
Ms.开头的名字”。
这说明:
- 列级约束只能限制一个字段;
- 元组级约束可以表达多个字段之间的关系。
5.4.6 元组约束的检查与违约处理
元组级约束同样会在以下时机被检查:
- 插入新元组;
- 修改元组中的相关属性值。
一旦不满足:
- 系统拒绝该操作;
- 表中不会出现违反业务规则的记录。
5.5 完整性约束命名子句
当约束越来越多时,如果不给约束起名字,后期维护会很麻烦。
因此 SQL 提供了完整性约束命名子句:
CONSTRAINT <完整性约束名> <完整性约束>
可以为:
NOT NULLUNIQUEPRIMARY KEYFOREIGN KEYCHECK
等约束起一个可识别的名字。
5.5.1 为什么要给约束命名
命名约束的主要作用有两个:
1. 便于维护和修改
如果约束有名字,就可以直接:
- 删除该约束;
- 修改该约束;
- 精确定位是哪个规则出错。
2. 提高可读性
一个名字好的约束,能让人快速知道它的用途。
例如:
StudentKeyTeacherFKeyC1C3
虽然 C1 这种名字也能用,但从工程角度看,语义化命名通常更好。
5.5.2 命名约束的定义示例
下面是课件中的学生表定义示例:
CREATE TABLE Student
(
Sno CHAR(8)
CONSTRAINT C1 CHECK (Sno BETWEEN '10000000' AND '29999999'),
Sname CHAR(20)
CONSTRAINT C2 NOT NULL,
Sbirthdate DATE
CONSTRAINT C3 CHECK (Sbirthdate > '1980-1-1'),
Ssex CHAR(6)
CONSTRAINT C4 CHECK (Ssex IN ('男', '女')),
Smajor VARCHAR(40),
CONSTRAINT StudentKey PRIMARY KEY (Sno)
);
这个例子表达了以下业务语义:
- 学号范围受限制;
- 姓名不能为空;
- 出生日期必须在 1980 年之后;
- 性别只能是男或女;
Sno是主码。
5.5.3 表级命名约束示例
有些约束适合定义在表级,例如涉及多个字段时。
课件中的教师表示例:
CREATE TABLE TEACHER
(
Eno CHAR(8) PRIMARY KEY,
Ename VARCHAR(20),
Job CHAR(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)
);
这里最后一个 CHECK 是典型的表级约束,因为它依赖两个属性:
SalDeduct
它的含义是:
教师应发工资(工资 + 扣除项)不得低于 3000 元。
这个例子也提醒我们:
- 单字段限制一般写成列级约束;
- 多字段联动规则一般写成表级约束。
5.5.4 修改表中的完整性限制
当业务规则发生变化时,可以使用 ALTER TABLE 修改约束。
1. 删除约束
例如,去掉出生日期限制:
ALTER TABLE Student DROP CONSTRAINT C3;
2. 先删后加,完成约束修改
例如:
- 原先要求学号在
10000000~29999999; - 现在改成
900000~999999; - 同时把出生日期限制改为 1985 年之后。
可以这样做:
ALTER TABLE Student
DROP CONSTRAINT C1;
ALTER TABLE Student
ADD CONSTRAINT C1 CHECK (Sno BETWEEN '900000' AND '999999');
ALTER TABLE Student
DROP CONSTRAINT C3;
ALTER TABLE Student
ADD CONSTRAINT C3 CHECK (Sbirthdate > '1985-1-1');
这里体现出命名约束的价值:
- 没有约束名时,修改会麻烦很多;
- 有约束名后,可以精准删除和重建。
5.7 触发器
触发器是本章中最“强力”的完整性控制工具。
如果说:
PRIMARY KEY、FOREIGN KEY、CHECK解决的是静态约束;- 那么触发器更擅长处理复杂的、动态的、事件驱动的约束与控制逻辑。
5.7.1 触发器的概念
触发器(Trigger)是:
用户定义在关系表上的一类由事件驱动的特殊过程。
这里有几个关键词一定要抓住:
1. 定义在表上
触发器是和某张基本表绑定的。
2. 由事件驱动
只有当某种事件发生时,它才会被自动激活。
常见触发事件有:
INSERTDELETEUPDATE
3. 自动执行
触发器存放在数据库服务器中,不需要用户手工调用。
只要触发事件发生,服务器就自动执行相应的触发器。
4. 可用于更复杂的完整性控制
很多规则无法仅靠 CHECK、PRIMARY KEY 等简单约束表达,这时就可以交给触发器处理。
5.7.2 触发器的作用
课件强调,触发器可以:
- 实施更复杂的检查和操作;
- 具有更精细、更强大的数据控制能力;
- 在数据库核心层实现集中控制。
这意味着:
- 不必把所有逻辑都写在应用程序中;
- 可以把关键规则直接写在数据库里;
- 无论哪个应用访问数据库,都必须遵守这些规则。
这是一种非常强的“一处定义,统一生效”的机制。
5.7.3 触发器的典型使用场景
触发器常见的用途包括:
- 复杂完整性检查;
- 自动生成日志;
- 级联维护辅助表;
- 审计用户操作;
- 实现某些业务规则;
- 自动补充或修正数据。
课件中的示例就是典型的“分数变动日志”场景。
5.7.4 定义触发器的语法结构
课件给出的通用语法格式为:
CREATE TRIGGER <触发器名>
{BEFORE | AFTER} <触发事件> ON <表名>
REFERENCING NEW | OLD ROW AS <变量>
FOR EACH {ROW | STATEMENT}
[WHEN <触发条件>]
<触发动作体>
下面逐项理解。
5.7.5 触发器定义中的关键组成部分
1. 触发器名
- 同一模式下必须唯一;
- 可以包含模式名,也可以不包含;
- 通常和所属表在同一模式中。
2. 表名
触发器只能定义在基本表上,不能定义在视图上。
3. 触发事件
可以是:
INSERTDELETEUPDATE- 或者它们的组合
还可以写成:
UPDATE OF Grade
表示只有当某些指定列被修改时才激活触发器。
4. 触发时机:BEFORE 与 AFTER
BEFORE:先执行触发器动作体,再执行触发事件对应的 SQL 操作;AFTER:先执行 SQL 操作,再执行触发器动作体。
理解这点非常关键。
例如:
- 想在写入前先检查或修正数据,适合用
BEFORE; - 想在写入成功后记录日志,适合用
AFTER。
5. 触发器类型:行级与语句级
行级触发器:FOR EACH ROW
受影响多少行,就触发多少次。
例如,一条 UPDATE 修改了 10 行数据,则行级触发器执行 10 次。
语句级触发器:FOR EACH STATEMENT
无论 SQL 语句影响多少行,整个语句只触发一次。
所以:
- 处理“每行都要检查”的逻辑,用行级;
- 处理“整条语句级别”的逻辑,用语句级。
6. 触发条件:WHEN
只有当条件成立时,动作体才执行。
如果省略 WHEN,只要触发器被激活,动作体就立即执行。
7. 触发动作体
动作体可以是:
- 一个匿名过程块;
- 或对已定义存储过程的调用。
如果动作体执行失败,则:
- 激活触发器的事件操作也会终止;
- 相关数据变化通常不会生效。
这说明触发器和原始 SQL 操作在逻辑上往往是绑定的。
5.7.6 OLD 与 NEW 的含义
在行级触发器中,系统通常允许引用两类值:
OLD:修改前或删除前的旧值;NEW:插入后或修改后的新值。
它们对理解触发器非常重要。
例如,在 UPDATE 触发器中:
OLD.Grade表示修改前成绩;NEW.Grade表示修改后成绩。
注意:
- 行级触发器中可以使用
OLD、NEW; - 语句级触发器中一般不能直接这样引用。
5.7.7 触发器定义示例:成绩增长超过 10% 时记录日志
课件中的示例要求:
当
SC表中Grade被修改时,如果新成绩比旧成绩增加了 10% 及以上,就把本次修改记录到日志表SC_U中。
日志表如下:
SC_U(Sno CHAR(8), Cno CHAR(5), Oldgrade SMALLINT, Newgrade SMALLINT)
触发器定义如下:
CREATE TRIGGER SC_T
AFTER UPDATE ON SC
REFERENCING
OLD AS OldTuple,
NEW 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.7.8 如何理解这个触发器
这个例子可以按以下顺序理解:
第一步:触发事件是什么
AFTER UPDATE ON SC
说明:
- 当
SC表发生更新时触发; - 并且是在更新操作执行之后触发。
第二步:是行级还是语句级
FOR EACH ROW
说明:
- 每修改一行,就分别判断一次;
- 若一次
UPDATE影响多行,则多次触发。
第三步:条件是什么
WHEN (NewTuple.Grade >= 1.1 * OldTuple.Grade)
表示只有当:
- 新成绩至少达到旧成绩的 110%
时,才执行动作体。
第四步:动作是什么
把旧成绩和新成绩写入日志表 SC_U。
也就是说,这个触发器的本质是:
自动监控成绩异常上涨,并记录变化痕迹。
这类设计在审计、风控、异常检测中都很常见。
5.7.9 触发器的执行过程
课件给出的执行顺序是:
1. 执行该表上的 BEFORE 触发器
2. 执行激活触发器的 SQL 语句
3. 执行该表上的 AFTER 触发器
所以整体流程是:
先前置处理,再执行原操作,最后后置处理。
这个顺序必须记住,因为它直接决定:
- 什么时候可以改数据;
- 什么时候能读取修改后的结果;
- 什么时候适合做日志记录。
5.7.10 删除触发器
删除触发器的语法为:
DROP TRIGGER <触发器名> ON <表名>;
只有已经存在的触发器才能被删除,而且通常需要相应权限。
5.7.11 触发器与一般约束的关系
可以把本章的约束工具分层理解:
第一层:基础完整性约束
PRIMARY KEYFOREIGN KEYNOT NULLUNIQUECHECK
这类约束:
- 声明简单;
- 语义清楚;
- 由系统自动高效检查;
- 适合表达静态规则。
第二层:触发器
触发器:
- 更灵活;
- 更强大;
- 能表达条件、时机、动作、日志等复杂逻辑;
- 适合表达“事件发生时要做什么”。
所以,实际设计中通常遵循一个原则:
能用普通完整性约束表达的,就优先不用触发器;只有当规则复杂到普通约束不足以表达时,再考虑触发器。
因为触发器越多,系统行为越隐蔽,维护难度也会随之上升。
5.8 本章核心知识总结
5.8.1 本章主线
本章围绕“数据库如何保证数据始终合法”展开,核心主线是:
- 什么是完整性;
- 完整性和安全性的区别;
- 实体完整性:保证主码唯一且非空;
- 参照完整性:保证表间引用关系正确;
- 用户定义的完整性:保证具体业务规则成立;
- 命名约束:便于维护和修改;
- 触发器:处理更复杂的动态规则。
5.8.2 必须掌握的几个关键词
1. 完整性
数据的正确性与相容性。
2. 实体完整性
主码必须:
- 唯一;
- 非空。
3. 参照完整性
外码必须正确引用被参照表中的主码,不能产生悬空引用。
4. 用户定义的完整性
业务系统中特有的语义规则。
5. 约束命名
通过 CONSTRAINT 给约束起名,便于后续维护。
6. 触发器
由事件驱动、自动执行的特殊过程,可实现复杂控制。
5.8.3 本章最容易混淆的点
1. 完整性 vs 安全性
- 完整性:防止“错误数据”
- 安全性:防止“非法用户和非法操作”
2. 主码 vs 唯一约束
- 主码:唯一 + 非空,是表中元组的标识;
UNIQUE:主要强调不重复,不一定承担实体标识作用。
3. 列级约束 vs 表级约束
- 列级约束:通常描述一个属性自身的限制;
- 表级约束:通常描述多个属性之间的关系。
4. 行级触发器 vs 语句级触发器
- 行级:影响几行触发几次;
- 语句级:整条语句只触发一次。
5. BEFORE vs AFTER
BEFORE:原操作之前执行;AFTER:原操作之后执行。
5.8.4 从应用角度理解本章
如果把数据库看成一个真实业务系统的“数据中枢”,那么完整性机制的作用就是:
- 不让错误数据进来;
- 不让不一致数据留下;
- 不让关键业务规则被破坏;
- 在复杂更新中自动做检查和补救;
- 在必要时留下日志痕迹。
所以,本章的本质不是“背几个 SQL 关键字”,而是理解:
数据库必须把现实业务规则固化为系统规则,自动检查,自动执行,自动维护。
这正是数据库区别于普通文件系统的重要原因之一。
5.9 本章学习结论
第5章的核心可以压缩成一句话:
数据库完整性就是通过主码、外码、检查约束、命名约束和触发器等机制,保证数据始终满足现实世界和业务系统规定的语义规则。
进一步拆开看:
- 实体完整性解决“每条记录能否被唯一识别”;
- 参照完整性解决“表与表之间的引用是否真实有效”;
- 用户定义的完整性解决“具体业务规则是否被满足”;
- 约束命名与修改机制解决“约束如何维护”;
- 触发器解决“复杂、动态、事件驱动的控制逻辑”。
学完这一章后,应该形成一个清晰认识:
数据库不是简单地存数据,而是要主动约束数据,使其始终保持正确、一致、可信。