范式

数据库的设计范式是数据库设计所需要满足 的规范,满足这些规范的数据库是简洁的、结构明晰的,同时,不会发生插入(insert)、删除(delete)和更新(update)操作异常。反之则 是乱七八糟,不仅给数据库的编程人员制造麻烦,而且面目可憎,可能存储了大量不需要的冗余信息。

  设计范式是不是很难懂呢?非也,大学教材上给我们一堆数学公式我们当然看不懂,也记不住。所以我们很多人就根本不按照范式来设计数据库。

  实质上,设计范式用很形象、很简洁的话语就能说清楚,道明白。本文将对范式进行通俗地说明,并以笔者曾经设计的一个简单论坛的数据库为例来讲解怎样将这些范式应用于实际工程。

  范式说明

  第一范式(1NF):数据库表中的字段都是单一属性的,不可再分。这个单一属性由基本类型构成,包括整型、实数、字符型、逻辑型、日期型等。

  例如,如下的数据库表是符合第一范式的:
字段1 字段2 字段3 字段4


  而这样的数据库表是不符合第一范式的:
字段1 字段2 字段3 字段4
字段3.1 字段3.2

  很显然,在当前的任何关系数据库管理系统(DBMS)中,傻瓜也不可能做出不符合第一范式的数据库,因为这些DBMS不允许你把数据库表的一列再分成二列或多列。因此,你想在现有的DBMS中设计出不符合第一范式的数据库都是不可能的。

  第二范式(2NF):数据库表中不存在非关键字段对任一候选关键字段的部分函数依赖(部分函数依赖指的是存在组合关键字中的某些字段决定非关键字段的情况),也即所有非关键字段都完全依赖于任意一组候选关键字。




  假定选课关系表为SelectCourse(学号, 姓名, 年龄, 课程名称, 成绩, 学分),关键字为组合关键字(学号, 课程名称),因为存在如下决定关系:

  (学号, 课程名称) → (姓名, 年龄, 成绩, 学分)

  这个数据库表不满足第二范式,因为存在如下决定关系:

  (课程名称) → (学分)

  (学号) → (姓名, 年龄)

  即存在组合关键字中的字段决定非关键字的情况。

  由于不符合2NF,这个选课关系表会存在如下问题:

  (1) 数据冗余:

  同一门课程由n个学生选修,"学分"就重复n-1次;同一个学生选修了m门课程,姓名和年龄就重复了m-1次。

  (2) 更新异常:

  若调整了某门课程的学分,数据表中所有行的"学分"值都要更新,否则会出现同一门课程学分不同的情况。

  (3) 插入异常:

  假设要开设一门新的课程,暂时还没有人选修。这样,由于还没有"学号"关键字,课程名称和学分也无法记录入数据库。

  (4) 删除异常:

  假设一批学生已经完成课程的选修,这些选修记录就应该从数据库表中删除。但是,与此同时,课程名称和学分信息也被删除了。很显然,这也会导致插入异常。

  把选课关系表SelectCourse改为如下三个表:

  学生:Student(学号, 姓名, 年龄);

  课程:Course(课程名称, 学分);

  选课关系:SelectCourse(学号, 课程名称, 成绩)。

  这样的数据库表是符合第二范式的,消除了数据冗余、更新异常、插入异常和删除异常。

  另外,所有单关键字的数据库表都符合第二范式,因为不可能存在组合关键字。

   第三范式(3NF):在第二范式的基础上,数据表中如果不存在非关键字段对任一候选关键字段的传递函数依赖则符合第三范式。所谓传递函数依赖,指的是如 果存在"A → B → C"的决定关系,则C传递函数依赖于A。因此,满足第三范式的数据库表应该不存在如下依赖关系:

  关键字段 → 非关键字段x → 非关键字段y

  假定学生关系表为Student(学号, 姓名, 年龄, 所在学院, 学院地点, 学院电话),关键字为单一关键字"学号",因为存在如下决定关系:

  (学号) → (姓名, 年龄, 所在学院, 学院地点, 学院电话)

  这个数据库是符合2NF的,但是不符合3NF,因为存在如下决定关系:

  (学号) → (所在学院) → (学院地点, 学院电话)

  即存在非关键字段"学院地点"、"学院电话"对关键字段"学号"的传递函数依赖。

  它也会存在数据冗余、更新异常、插入异常和删除异常的情况,读者可自行分析得知。

  把学生关系表分为如下两个表:

  学生:(学号, 姓名, 年龄, 所在学院);

  学院:(学院, 地点, 电话)。

  这样的数据库表是符合第三范式的,消除了数据冗余、更新异常、插入异常和删除异常。

  鲍依斯-科得范式(BCNF):在第三范式的基础上,数据库表中如果不存在任何字段对任一候选关键字段的传递函数依赖则符合第三范式。

  假设仓库管理关系表为StorehouseManage(仓库ID, 存储物品ID, 管理员ID, 数量),且有一个管理员只在一个仓库工作;一个仓库可以存储多种物品。这个数据库表中存在如下决定关系:

  (仓库ID, 存储物品ID) →(管理员ID, 数量)

  (管理员ID, 存储物品ID) → (仓库ID, 数量)

  所以,(仓库ID, 存储物品ID)和(管理员ID, 存储物品ID)都是StorehouseManage的候选关键字,表中的唯一非关键字段为数量,它是符合第三范式的。但是,由于存在如下决定关系:

  (仓库ID) → (管理员ID)

  (管理员ID) → (仓库ID)

  即存在关键字段决定关键字段的情况,所以其不符合BCNF范式。它会出现如下异常情况:

  (1) 删除异常:

  当仓库被清空后,所有"存储物品ID"和"数量"信息被删除的同时,"仓库ID"和"管理员ID"信息也被删除了。

  (2) 插入异常:

  当仓库没有存储任何物品时,无法给仓库分配管理员。

  (3) 更新异常:

  如果仓库换了管理员,则表中所有行的管理员ID都要修改。

  把仓库管理关系表分解为二个关系表:

  仓库管理:StorehouseManage(仓库ID, 管理员ID);

  仓库:Storehouse(仓库ID, 存储物品ID, 数量)。

  这样的数据库表是符合BCNF范式的,消除了删除异常、插入异常和更新异常。
范式应用

  我们来逐步搞定一个论坛的数据库,有如下信息:

  (1) 用户:用户名,email,主页,电话,联系地址

  (2) 帖子:发帖标题,发帖内容,回复标题,回复内容

  第一次我们将数据库设计为仅仅存在表:
  
用户名 email 主页 电话 联系地址 发帖标题 发帖内容 回复标题 回复内容

  这个数据库表符合第一范式,但是没有任何一组候选关键字能决定数据库表的整行,唯一的关键字段用户名也不能完全决定整个元组。我们需要增加"发帖ID"、"回复ID"字段,即将表修改为:
用户名 email 主页 电话 联系地址 发帖ID 发帖标题 发帖内容 回复ID 回复标题 回复内容

  这样数据表中的关键字(用户名,发帖ID,回复ID)能决定整行:

  (用户名,发帖ID,回复ID) → (email,主页,电话,联系地址,发帖标题,发帖内容,回复标题,回复内容)

  但是,这样的设计不符合第二范式,因为存在如下决定关系:

  (用户名) → (email,主页,电话,联系地址)

  (发帖ID) → (发帖标题,发帖内容)

  (回复ID) → (回复标题,回复内容)

  即非关键字段部分函数依赖于候选关键字段,很明显,这个设计会导致大量的数据冗余和操作异常。




  我们将数据库表分解为(带下划线的为关键字):

  (1) 用户信息:用户名,email,主页,电话,联系地址

  (2) 帖子信息:发帖ID,标题,内容

  (3) 回复信息:回复ID,标题,内容

  (4) 发贴:用户名,发帖ID

  (5) 回复:发帖ID,回复ID

  这样的设计是满足第1、2、3范式和BCNF范式要求的,但是这样的设计是不是最好的呢?

  不一定。

   观察可知,第4项"发帖"中的"用户名"和"发帖ID"之间是1:N的关系,因此我们可以把"发帖"合并到第2项的"帖子信息"中;第5项"回复"中 的"发帖ID"和"回复ID"之间也是1:N的关系,因此我们可以把"回复"合并到第3项的"回复信息"中。这样可以一定量地减少数据冗余,新的设计为:

  (1) 用户信息:用户名,email,主页,电话,联系地址

  (2) 帖子信息:用户名,发帖ID,标题,内容

  (3) 回复信息:发帖ID,回复ID,标题,内容

  数据库表1显然满足所有范式的要求;

  数据库表2中存在非关键字段"标题"、"内容"对关键字段"发帖ID"的部分函数依赖,即不满足第二范式的要求,但是这一设计并不会导致数据冗余和操作异常;

  数据库表3中也存在非关键字段"标题"、"内容"对关键字段"回复ID"的部分函数依赖,也不满足第二范式的要求,但是与数据库表2相似,这一设计也不会导致数据冗余和操作异常。

  由此可以看出,并不一定要强行满足范式的要求,对于1:N关系,当1的一边合并到N的那边后,N的那边就不再满足第二范式了,但是这种设计反而比较好!

  对于M:N的关系,不能将M一边或N一边合并到另一边去,这样会导致不符合范式要求,同时导致操作异常和数据冗余。
对于1:1的关系,我们可以将左边的1或者右边的1合并到另一边去,设计导致不符合范式要求,但是并不会导致操作异常和数据冗余。

  结论

  满足范式要求的数据库设计是结构清晰的,同时可避免数据冗余和操作异常。这并意味着不符合范式要求的设计一定是错误的,在数据库表中存在1:1或1:N关系这种较特殊的情况下,合并导致的不符合范式要求反而是合理的。

  在我们设计数据库的时候,一定要时刻考虑范式的要求。

 

 

 

 

传递依赖、部分依赖与范式     -|鹤云逸 发表于 2007-8-8 14:22:00

     范式建立在函数依赖的基础之上,对传递依赖、部分依赖的准确理解直接影响范式的判定。那么什么是传递依赖?什么是部分依赖?下面以例题来说明。

●设有一个关系模式R(A,B,C,D),函数依赖集F={A→B,B→C,C→D,D→A},那么R共有(1)个侯选码。R的每个属性都不可分,其可达到的最高范式是(2) 。

供选择的答案:

(1)A. 1               B. 2            C. 3        D. 4

(2)A. 2NF             B. 3NF          C. BCNF     D. 1NF

试题分析:

    只要能推导出整个属性组U={A,B,C,D},况且没有多余元素就是候选码。在这个关系模式中,单个的A、B、C、D都能推导出U,况且只有自身一个元素无多余元素,所以都是候选码。

    传递依赖的完整定义:设有关系模式R(U),X、Y、Z是属性或属性组,如果有X→Y,Y→Z,而且Y→X不成立、Z不是Y的子集、Z不是X的子集。注意,而且后面的三个条件必须都成立,才能称之为传递依赖。

很多人可能误认为R中存在传递依赖如,由A→B、B→C尽管可以得出A→C,但是C并不传递依赖A,因为由C→D、D→A可知C→A,不满足传递依赖定义中的条件“Y→X不成立”。但是,A→C是成立的,因为它可根据Armstrong推理系统中的传递律(注意,不是传递依赖,不要把两者搞混了),只是不符合传递依赖的定义罢了。

    根据BCNF的定义可知,满足BCNF定义的关系模式中的每一个函数依赖的左部都含有候选码,显然,R满足BCNF的要求。

试题答案:

    (1)D          (2)C

 

第(2)空,很多人会选择2NF。下面再看一个例题:

 

●已知关系R(A,B,C,D,E)和函数依赖集F为{AB→D,BC→E,D→C},R的每个属性均不可分,则R能满足的最高范式是(3) 。

供选择的答案:

(3)A. 1NF         B. 2NF          C. 3NF          D. BCNF

试题分析:

    根据函数依赖集能推导出全部属性,而且没有多余属性的属性或者属性组就是候选码。由AB→D、D→C可得AB→C,根据Armstrong推理系统中的自反律、合并规则可得AB→BC,又BC→E,所以AB→E,因此AB能推出所有属性,而且不含多余属性,所以它是候选码。R只有AB这一个候选码。显然,非主属性C、E都是传递依赖于AB,所以R不是3NF。

    那么非主属性E是否部分函数依赖于码AB呢?首先来看部分函数依赖的定义:

在关系模式R(U)中,对于属性组X、Y,如果有X→Y,并且对于X的任何一个真子集X’都不能得出X’ →Y,则称Y对X完全函数依赖。而如果X→Y,但Y不完全函数依赖于X,则称Y对X部分函数依赖。可见部分函数依赖是在完全函数依赖的基础上定义的,也就是说不满足完全函数依赖就是部分函数依赖。

该题中,根据Armstrong推理系统可得AB→E,属性组AB的真子集有A、B、空集φ,但由单个的A不能得出A→E,由单个的B也不能得出B→E,空集φ显然也不能得出φ→E,所以E是完全函数依赖AB。很多人在确定候选码为AB后,一看到函数依赖集中有BC→E,就误认为存在部分函数依赖,这是一个常见的误区,须仔细揣摩,理解须准确到位。

同理,可以得出其它非主属性C、D都是完全函数依赖码AB,所以R∈2NF。

试题答案:

    (3)B 

 

 

P-V操作定义:

假设sem是个整型变量。
P原语的主要操作是:

(1)sem减1;

(2)若sem减1后仍大于或等于零,则该进程继续执行;

(3)若sem减1后小于零,则该进程被阻塞,在相应队列中排队,然后转向系统的进程调度。

V原语的主要操作是:

(1)sem加1;

(2)若相加结果大于零,则进程继续执行;

(3)若相加结果小于或等于零,则唤醒一阻塞在该信号量上的进程,然后再返回原进程继续执行或转进程调度。

典型理解偏差:

1.        以V原语的1、2步来做,Sem不就永远大于0,那进程不就一直循环执行成为死循环了?

2.        Sem大于0那就表示有临界资源可供使用,为什么不唤醒进程?

3.        Sem小于0应该是说没有临界资源可供使用,为什么还要唤醒进程?

4.        如果是互斥信号量的话,应该设置信号量Sen=1,但是当有5个进程都访问的话,最后在该信号量的链表里会有4个在等待,也是说S=-4,那么第一个进程执行了V操作使S加1,释放了资源,下一个应该能够执行,但唤醒的这个进程在执行P操作时因S<0 ,也还是执行不了,这是怎么回事呢?

5.        Sem的绝对值表示等待的进程数,同时又表示临界资源,这到底是怎么回事?

析疑:

1.       P操作对sem减1的。P、V原语必须成对使用!从而不会造成死循环。

2.       Sem大于0的确表示有临界资源可供使用,而且这个时候没有进程被阻塞在这个资源上,也就是说没有进程因为得不到这类资源而阻塞,所以没有被阻塞的进程,自然不需要唤醒。

3.       V原语操作的本质在于:一个进程使用完临界资源后,释放临界资源,使Sem加1,以通知其它的进程,这个时候如果Sem<0,表明有进程阻塞在该类资源上,因此要从阻塞队列里唤醒一个进程来“转手”该类资源。比如,有2个某类资源,三个进程A、B、C、D要用该类资源,最开始Sem=2,当A进入,Sem=1,当B进入Sem=0,表明该类资源刚好用完,当C进入时Sem=-1,表明有一个进程被阻塞了,D进入,Sem=-2。当A用完该类资源时,进行V操作,Sem=-1,释放该类资源,而这时Sem<0,表明有进程阻塞在该类资源上,于是唤醒一个。

4.       当一个进程阻塞了的时候,它已经执行过了P操作,并卡在临界区那个地方。当唤醒它时就立即进入它自己的临界区,并不需要执行P操作了,当执行完了临界区的程序后,就执行V操作。

5.       当信号量Sem小于0时,其绝对值表示系统中因请求该类资源而被阻塞的进程数目。S大于0时表示可用的临界资源数。注意在不同情况下所表达的含义不一样。当等于0时,表示刚好用完。


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值