数据库的完整性是指数据库的正确性和相容性。正确性是指符合现实世界的语义、反映当前实际状况;数据相容性是指数据库中同一对象在不同关系表中的数据是符合逻辑的。数据库的完整性是为了防止数据库中存在不正确的数据,数据库的安全性是防止恶意破坏和非法获取。
为维护数据库完整性数据库管理系统必须能实现以下几个功能:
- 提供定义完整性约束条件的机制
- 提供完整性检查方法
- 进行违约处理
1.实体完整性
定义实体完整性
定义实体完整性即给表设定主键(primary key),即在创建表的时候定义主键。
例
-- 第一种 列级定义
create table table_name(
id int primary key,
name varchar(10),
address varchar(50)
)
-- 第二种(可以定义多个) 表级定义
create table table_name(
id int,
name varchar(10),
primary key(id,name)
)
给已有表添加主键
alter table_name add constraint pk_table_name primary key('field')
实体完整性检查和违约处理
用primary key
短语定义了关系的主码之后,每当用户程序对基本表插入或修改对应数据操作时,关系数据库系统都会依照实体完整性做以下检查:
- 检查主码是否唯一,如果不唯一则拒绝插入或修改
- 检查主码的各个属性是否为空,只要有一个为空就拒绝插入或修改(这一条个人感觉跟现在数据库不是这样的,像SQL是定义了非空约束如果为空则拒绝插入或修改)
检查主码是否唯一的一种方法是进行全盘扫描,依次检查判断每一条记录的主码。然而全表扫描是十分耗时的,为了避免全盘扫描,关系数据库系统一般都在主码上自动建立一个索引(如B+树索引),通过索引查找查找表中是否已经存在该主码将大大提高效率。
2.参照完整性
定义参照完整性
定义参照完整性其实就是给表添加外键约束。通过foreign key
短语定义哪些类为外码,用references
短语指明这些外码参照哪个表的主码
例.
create table table course(
id int primary key,
name varchar(10)
)
create table stu(
id int not null,
name varchar(10),
cId int,
foreign key(cId) references course(id)
)
给已有表添加外键
create table table course(
id int primary key,
name varchar(10)
)
create table stu(
id int not null,
name varchar(10),
cId int
)
alter table stu add constraint stu_cId_fk_course_id foreign key(cId) references course(id)
参照完整性检查和违法处理
参照完整性将两个表中的相应元组联系起来了。因此,对被参照表和参照表进行增、删、改时可能破坏参照完整性,因此必须进行检查以保证两个表的相容性。
按以上创建定义完整性为例(stu为参照表,course为被参照表),可能有四种可能破坏参照完整性的情况:
- 学生表(stu)中添加一个学生,但是学生所学的课程在课程表(course)中没有。
- 在学生表(stu)中修改课程Cid,但是课程表(course)没有对于的id
- 在课程表中删除一门课程,造成学生表中Cid在课程表中没有id与之相等。
- 修改课程表id,造成学生表中Cid在课程表中没有id与之相等。
当上述几种情况发生时,系统可以采用以下策略处理:
- 拒绝(
NO ACTION
)执行:默认策略 - 级联(
CASCADE
)操作
当删除或修改被参照表(course)的一个元祖导致与参照表(stu)不一致时,删除或修改参照表中的所有不一致的元组。 - 设置为空值
当删除或被参照表(course)的一个元祖导致与参照表(stu)不一致时,将参照表中所有不一致的属性设置为空值。
对于参照完整性除了定义外码还应该定义外码是否允许为空值
3.用户定义的完整性
用户定义的完整性是针对某一具体应用的数据必须满足的语义要求。
属性上的约束条件
在create table
中定义属性的同时,可以根据需求定义属性上的约束条件,即属性值限制,一般有:
- 列值非空(not null)
- 列值唯一(unique)
- 检查列值是否满足某一个条件表达式(check短语,mysql不支持)
元组上的约束条件
与属性的约束条件类似,在create table
语句中用check短语定义元组上的约束条件。
例.定义学生表,当学生是“男”时,其名字不能以“Ms”开头。
create table stu(
id int,
name varchar(20),
sex char(2),
check(sex='女' OR name not like 'Ms.%')
)
4.完整性约束命名子句
完整性命名子句其实就是用CONSTRAINT
关键字来定义完整性约束。
完整性命名子句
CONSTRAINT <完整性约束条件名> <完整性约束条件>
完整性约束条件包括NOT NULL
、UNIQUE
、PRIMARY KEY
、FOREIGN KEY
例.建立学生登记表stu,要求学生学号在90000~99999之间,姓名不能取空,年龄小于40,性别只能是“男” 或“女”
create table stu(
sno int constraint c1 check(sno between 90000 and 99999),
sname char(20) constraint c2 check not null,
sage int constraint c3 check(sage<40),
sex char(2) constraint c4 check(sex in("男","女")),
constraint pk_stu primary key(sno)
)
修改表中完整性限制
修改表中的完整性限制即删除限制或者添加限制。
删除限制
alter table stu drop constraint c4
添加限制
alter table add constraint c4 check(sex in("男","女"))
需要注意的是mysql不支持check约束,如果想使用check
约束请使用Sqlserver或者其他关系型数据库。
5.域中的完整性限制
一般地,域是一组具有相同数据类型的集合。SQL支持域的概念,可以用CREATE DOMAIN
语句建立一个域以及该域满足的完整性约束条件。
创建域并声明域的取值范围:
CREATE DOMAIN generderDomain char(2) constraint gd check(value in ("男","女"))
在创建表时使用上面创建的域:
create table stu(
id int,
name varchar(10),
sex generderDomain
)
6.断言
在SQL
中可以使用数据库定义语言CREATE ASSERTION
语句通过声明性断言来指定更具一般性的约束。可以简单理解为某个操作执行前的一个检查判断,如果结果为真,可以执行,结果为假拒绝执行。
创建断言
create ASSERTION <断言名> <check 子句>
例.限制数据库课程60名学生选修
create ASSERTION ASSE_SC_DB_NUM
CHECK(60>=(select count(*) from Course,Sc where Sc.no = course.cno and course.cname = '数据库'))
这里的Sc
是学生与选课的关系表,Course
是课程表,no
是学生学号
删除断言
DROP ASSERTION <断言名>
如果断言很复杂,则系统在检测和维护断言上的开销较高,这是在使用断言上应该注意的(mysql暂不支持断言)。
7.触发器
触发器是用户定义在关系表上的一类由事件驱动的特殊过程。一旦定义,触发器将被保存在数据库服务器中。任何用户的增删改操作均由服务器自动激活相应的触发器,在关系型数据库系统核心层进行集中的完整性控制。触发器类似于约束,但是比约束更加灵活,可以实施更加复杂的检查和操作。
触发器在SQL 99之后才写入SQL标准,但很多关系型数据库在很早之前就支持触发器,因此不同的关系型数据库实现触发器的语法各不相同、互补兼容。
定义触发器
触发器又叫做事件-条件-动作(event-condition-action)规则。当特定的系统事件发生时,对规则的条件进行检查,如果条件成立则执
行规则中的动作,否则不执行该动作。
语法:
CREATE TRIGGER <触发器名>
{BEFORE | AFTER} <触发事件> ON <表名> --指明触发器激活事件是执行触发事件的前或后
REFERENCING NEW|OLD ROW AS<变量> -- REFERENCING 指出引用的变量
FOR EACH {ROW | STATEMENT} -- 定义触发器的类型,指明动作执行的频率
[WHEN <触发条件>]<触发动作体>
语法说明
- 只有表的拥有者即创建表的用户才可以创建触发器,并且一个表只能创建一定数量的发出器。
- 触发器名
触发器名可以包含模式名,也可以不包含模式名。同一模式下,触发器名必须是唯一的,并且触发器名和表名必须在同一模式下。 - 表名
触发器只能定义在基本表上,不能定义在视图上。当基本表的数据发生变化时,将激活定义在该表上的相应触发器,因此该表也称为触发器的目标表。 - 触发事件
触发事件可以是INSET
、DELETE
、UPDATE
,也可以是这几个事件的组合。还可以是UPDATE OF<触发列,…>,即进一步指明修改哪些列时激活触发器。BEFORE | AFTER
是触发的时机,AFTER
表示触发事件的操作执行之后触发激活器,BEFORE
表示触发事件执行之前激活触发器。 - 触发器类型
触发器按照所触发动作的间隔尺寸可以分为 行级触发器(FOR EACH ROW)和语句级触发器(FOR EACH STATEMENT)
例.在TEACHER表上创建一个AFTER UPDATE触发器,触发事件是UPDATE语句:
UPDATE TEACHER SET Deptno=5;
假设表TEACHER有1000行
如果是语句级触发器,那么执行完该语句后,触发动作只发生一次
如果是行级触发器,触发动作将执行1000次
- 触发条件
触发器激活时,只有触发条件为真时触发动作体才执行,否则不执行。如果省略when
,则触发器激活时触发体立即执行。 - 触发动作体
触发动作体既可以是一个匿名PL/SQL
过程块,也可以是对已创建存储过程的调用。如果是行级触发器,用户都可以在过程体中使用NEW
和OLD
引用事件之后的新值和事件之前的旧值;如果是语句级触发器,则不能在触发动作体中使用NEW
或OLD
进行引用;如果触发动作体执行失败,激活触发器的事件就会终止执行,触发器的目标表或触发器可能影响的其他对象不发生任何变化
例1、当对表SC的Grade属性进行修改时,若分数增加了10%则将此次操作记录到下面表中:
SC_U(Sno,Cno,Oldgrade,Newgrade)
其中Oldgrade是修改前的分数,Newgrade是修改后的分数。
CREATE TRIGGER SC_T --创建触发器,触发器名SC_T
AFTER UPDATE OF Grade ON SC -- 触发事件的操作执行之后激活触发器,即表SC的grade属性修改后再触发下面的规则
REFERENCING --新值和旧值
OLD row AS OldTuple,
NEW row AS NewTuple
FOR EACH ROW -- 触发器类型为行级触发器
WHEN (NewTuple.Grade >= 1.1*OldTuple.Grade) -- 触发条件
INSERT INTO SC_U(Sno,Cno,OldGrade,NewGrade) --触发动作体插入操作
VALUES(OldTuple.Sno,OldTuple.Cno,OldTuple.Grade,NewTuple.Grade)
行级触发器(FOR EACH ROW)可以用OLD和NEW引用新值和老值,如果不是行级触发器可以使用NEWTABLE
和OLDTABLE
来表示原来的内容和新内容。
下面两个例子适用于mysql:
创建只有一个执行语句的触发器
CREATE TRIGGER 触发器名 BEFORE|AFTER 触发事件 ON 表名 FOR EACH ROW 执行语句;
例、创建了一个名为trig1的触发器,一旦在work表中有插入动作,就会自动往time表里插入当前时间
CREATE TRIGGER trig1 AFTER INSERT
ON work FOR EACH ROW
INSERT INTO time VALUES(NOW());
创建有多个执行语句的触发器
CREATE TRIGGER 触发器名 BEFORE|AFTER 触发事件
ON 表名 FOR EACH ROW
BEGIN
执行语句列表
END;
例、定义一个触发器,一旦有满足条件的删除操作,就会执行BEGIN和END中的语句
CREATE TRIGGER trig2 BEFORE DELETE
ON work FOR EACH ROW
BEGIN
INSERT INTO time VALUES(NOW());
INSERT INTO time VALUES(NOW());
END
激活触发器
触发器的执行是由触发事件激活,并由数据库服务器自动执行。一个数据表上可能有一个或多个触发器,它们激活时遵循如下执行顺序:
- 执行该表上的
BEFORE
触发器 - 激活触发器的
SQL
语句 - 执行该表上的
AFTER
触发器
一般来讲如果一个表上多个BEFORE
触发器,那么执行顺序规则是“谁先创建谁先执行”。但也有些关系型数据库是按照触发器名字字母的排序执行触发器。
删除触发器
语法如下:
DROP TRIGGER <触发器名> ON <表名>
触发器必须是一个已经创建的触发器,并且只能由具有相应权限的用户删除。