关系模型由三部分组成:关系数据结构、关系操作集合、关系完整性约束。数据结构回答"数据长什么样",操作回答"数据怎么算",完整性约束回答"什么样的数据算合法"。三者之中,约束最容易被考生忽视,却是数据库系统工程师考试的常驻考点。按教材的正式表述,关系模型的完整性约束分为三类:实体完整性、参照完整性、用户定义完整性。
实体完整性规则的标准定义是:若属性A是基本关系R的主属性,则属性A不能取空值。这里需要先厘清几个术语。码是关系中能唯一标识元组的属性或属性组,一个关系可能有多个候选码,选定其中一个作为主码,主码所包含的属性称为主属性,其余属性称为非主属性。实体完整性约束的对象是主属性,而不是仅仅约束主码列。这一点在复合主码的场景下尤其关键,后面误区部分会展开。所谓空值,就是"未知"或"不存在"的值,注意空值不等于零、不等于空字符串,它是数据库里的一个特殊标记。
参照完整性规则的标准定义是:设F是基本关系R的一个或一组属性,但不是R的码,Ks是基本关系S的主码,如果F与Ks相对应,则称F是R的外码,R为参照关系,S为被参照关系。R中每个元组在F上的取值必须满足:或者取空值,即F的每个属性值均为空;或者等于S中某个元组的主码值。通俗地说,外码的取值只能从被参照关系的主码值集合里选,或者干脆不取值。
用户定义完整性规则的定义是:针对某一具体应用的数据必须满足的语义要求,由应用环境决定。它是三大类中最灵活的一类,反映的是业务规则。比如某会员系统的业务规定"账户余额不能小于一百",这就是一条用户定义完整性约束,可以用SQL的CHECK约束来实现。
很多考生把关系模型简单理解成"表",这是片面的。一个完整的数据库系统至少要在两个层面保证数据正确:一是结构层面,二是语义层面。完整性约束机制正是语义层面的保障手段。它和数据结构、关系操作共同构成关系模型的三要素,三者缺一不可。考试中常以"下列哪项不属于关系模型组成部分"的形式考查这个框架,选项往往伪造出"关系完整性约束"之外的干扰项。
三大完整性在SQL标准中都有对应的实现机制。实体完整性对应主码约束,也就是PRIMARY KEY声明;参照完整性对应外码约束,也就是FOREIGN KEY加REFERENCES子句;用户定义完整性对应的手段最丰富,包括CHECK检查约束、NOT NULL非空约束、UNIQUE唯一约束、DEFAULT默认值约束,以及域定义、断言、触发器等一系列机制。理解这层对应关系,做题时看到SQL语句就能快速判断它在实现哪一类完整性。
备考中另一个高频混淆点是完整性约束与范式、触发器三者的关系。范式解决的是关系模式的结构质量,通过分解消除数据冗余和更新异常,它回答"表应该怎么设计才不冗余";完整性约束解决的是数据的合法性问题,它回答"什么样的值允许进入数据库";触发器则是一种事件驱动的程序机制,常被用来实现前两者表达不了的复杂规则。三者层次不同、目标不同,但命题人喜欢把它们放进同一语境。辨析口诀可以这样记:范式管结构,约束管取值,触发器管联动。一旦分清这条边界,遇到"以下哪项属于完整性约束范畴"的题目就不会再被触发器、范式相关的干扰项带偏。
完整性约束不是数据库设计者拍脑袋加上的规则,它的每一条都对应着现实世界的客观要求。理解其底层逻辑,比死记定义有效得多。
现实世界中的每个实体都是可区分的,两个人同名同姓,仍然是两个不同的实体。关系模型用元组来表示实体,就必须回答一个问题:用什么来区分两个元组?答案是码。码的取值一旦出现空值,就意味着"这个实体的标识未知",此时该元组既无法被其他关系引用,也无法与同关系中的其他元组区分,实体就失去了可区分性。因此实体完整性要求主属性不能取空值,其本质是保证元组在关系中的可标识性。唯一性同样是实体完整性的内在要求:主码的取值不仅不能为空,还必须在关系中唯一。DBMS通过主码上自动建立的唯一索引来高效执行这一检查,当插入或更新操作试图产生重复的主码值时,操作会被直接拒绝。非空与唯一这两条要求共同保证了主码能够承担"元组身份证"的职责。值得一提的是,码的候选属性组可能有多个,但被选为主码的只有一个,实体完整性约束只施加在被选定的主码上,其他候选码并不自动获得非空约束,除非另行声明。可以这样理解:实体完整性是关系模型对"每个实体必须能被唯一识别"这一客观事实的形式化表达。还需要注意,实体完整性只约束主属性,非主属性是否允许空值由具体语义决定,比如"系名"属性在逻辑设计不合理时可能暂时为空,这不违反实体完整性。
数据库中最有价值的信息往往藏在关系之间的联系里。学生关系里的一条元组记录了某个学生的信息,选课关系里的元组则记录了"某个学生选了某门课"这一事实。选课关系通过学号属性引用学生关系,学号就是选课关系的外码。如果选课关系里出现了一个学号,而学生关系里根本不存在这个学号对应的学生,那么这条选课记录就成了一条"悬空引用",它描述的是一个不存在的实体参与的选课行为,这在语义上是荒谬的。参照完整性就是要杜绝这种荒谬:外码要么为空,要么指向被参照关系中真实存在的主码值。空值被允许,是因为语义上允许"暂时不知道这个引用指向谁"的情况,比如新录入的选课记录还没有确定学生时。但一旦外码有值,就必须真实存在,这条底线保证了跨关系数据的一致性,也保证了关系连接运算的结果是有意义的。
完整性约束由数据库管理系统在数据变更时自动检查,这是它与应用程序层校验的本质区别。约束定义在表结构上,任何用户通过任何途径对数据进行的插入、删除、更新操作,都必须先通过DBMS的约束检查。检查时机按操作类型区分:执行插入操作时,要检查实体完整性、参照完整性和用户定义完整性,因为新元组的三类约束都可能被违反;执行删除操作时,一般只需检查参照完整性,因为删除元组不会破坏实体完整性,但可能让其他关系中的外码失去引用目标;执行更新操作时,三类完整性都要检查,更新主码时既要检查新主码值是否为空、是否唯一,还要检查是否有外码引用旧主码值。
检查发现违约时,DBMS的默认处理是拒绝执行,但SQL标准还提供了另外几种违约处理策略:级联操作,即连带修改或删除参照关系中对应的元组;置空值操作,即把引用方的外码值置为空;置默认值操作,即把引用方的外码值置为预先定义的默认值。这些策略通过外码定义中的ON DELETE或ON UPDATE子句来声明,构成了参照完整性的完整执行机制。
三类完整性各有应用场景,SQL中对应的实现手段也各不相同。掌握分类是为了辨析概念,掌握落地手段才是考试得分的关键。
实体完整性的实现依赖主码定义。定义单属性主码时,可以在属性定义后直接附加PRIMARY KEY;定义复合主码时,必须在表级约束中统一声明属性组。主码约束隐含了"非空且唯一"两个要求,一个表只能有一个主码。
参照完整性的实现依赖外码定义,格式为在属性或属性组后附加REFERENCES子句,指明被参照的表和属性。定义外码时有两个细节常考:一是外码引用的必须是被参照表的主码或具有唯一约束的属性;二是外码的列数必须与被参照的属性列数一致,且对应属性的值域兼容。
用户定义完整性的实现手段最为丰富。NOT NULL约束声明某属性不允许空值,用于必填字段;UNIQUE约束声明某属性的取值在关系中不得重复,用于需要唯一但又不是主码的字段,比如身份证号在员工表中往往用UNIQUE而不是主码;DEFAULT约束为属性指定默认值,插入时未显式赋值则自动填入;CHECK约束声明一个条件表达式,凡是插入或更新的元组都必须使该表达式为真,否则操作被拒绝,它适合表达取值范围的业务规则。此外还有域定义和断言等更高级的手段。
完整性约束贯穿数据库设计的全过程。在需求分析阶段,业务规则被整理出来,比如"余额不能小于一百""学号必须唯一",这些规则就是用户定义完整性的素材来源;在概念设计阶段,E-R模型中的实体、联系和属性为完整性约束提供了语义基础,实体完整性对应实体标识,参照完整性对应实体之间的联系;在逻辑设计阶段,E-R模型转换为关系模式,主码、外码随之确定,三类完整性约束被正式落实为关系模式定义的一部分;在物理设计阶段,DBMS依据约束自动建立索引、实施检查。理解这个贯穿性,就能明白为什么完整性约束被列为关系模型三要素之一,而不是可有可无的附加规则。