设计数据库设计说明书的一个问题

&&& Richie
&&& RicCC:
&&& 这是某个系统的一个做法,觉得对架构师在系统分析设计思想上有所启发,所以写出来跟大家分享。
&&& 一个大系统,业务和系统都非常复杂,系统很灵活,但是后台提交到数据库的SQL语句,基本都是最简单的SELECT、DELETE、UPDATE、INSERT,你看不到复杂语句的出现,包括JOIN。而对于我们自己开发的系统,哪怕再简单不过,不使用JOIN似乎不可能。
&&& 用一个简单的例子来说明。有一个销售订单对象,它会关联到客户对象。我们假设客户对象以一个客户编码作为主键,值是唯一的,然后会有客户名称、地区/城市、地址等资料。销售订单表有一个客户编码字段,跟客户对象关联。用户需求可能是通过客户名称、地区/城市等相关信息查询销售订单。
&&& 这种情况下,有一种设计方案,是在SalesOrder表中建立一个CustomerName字段,销售订单创建的时候就把客户名称的值带过去,在查询上只允许按照客户名称对SalesOrder表进行查询,避免使用SalesOrder表与Customer关联。这种方案的优缺点,大家会有各自的看法。首先,如果需要添加按照客户地区/城市来查询SalesOrder,得在SalesOrder添加客户地区/城市的字段,如果还有其它的对象跟SalesOrder关联,可能也得添加字段用于查询;其次某些情况下,这种用法能够跟业务需求比较好的吻合起来,但大部分情况下,如果客户名称有发生变化,怎么办?维持SalesOrder表中原有的客户名称值不变,还是对所有添加了客户名称的表进行同步更新?如果不是因为业务需求确实需要这样做,这种做法是不大合理的。
&&& 接下来看一些比较常规的做法。直接写SQL,使用SalesOrder Inner Join Customer进行查询,或者使用HQL,或者在ORM上配置对象关联关系等方式实现。
&&& 首先,如果在ORM上配置关联关系实现,一旦查询复杂一些,并且系统这种查询很多,会使系统实现或配置很复杂。所以对于这种情况,ORM都会尽量避免,要么直接使用SQL,或者象Hibenate使用HQL实现。
&&& 直接使用SQL时,一方面造成业务模型对逻辑封装的泄漏,另一方面是对数据持久化媒体的直接暴露。使用HQL能够做到一些弥补。
&&& 最后我们从性能和效率方面分析一下。
&&& 不管是ORM配置对象关联关系来实现,还是使用HQL,最终框架都是生成Join语句进行数据库查询。SalesOrder和Customer只是一个最简单的示例,实际中大家遇到的关联需求可能极为复杂。因为业务的复杂性,对关联表中索引的需求也是多样的,难以保证每个查询以最高的效率使用索引。比较极端的情况,也很可能常遇到,象上面的例子,可能需要先对SalesOrder和Customer两个表做全表扫描,然后对这两个表所有数据进行Join操作。假若SalesOrder和Customer都是几十万、几百万的数据量(当然这种数据量下肯定不会让这种事情发生,只是举例),这个查询性能会是什么样子?SQL Server也好Oracle也好,都会非常差的。可能不少的系统都会面临一些复杂查询的性能问题,解决起来确实比较棘手。
&&& 在数据量没有这么多,能够在一定程度利用索引的时候,情况会好一些,但这也只是限于一定的情况下。各方面的原因,都可能造成我们难以确保所有复杂查询都充分利用索引,随着业务数据的变化,索引的使用效率也会发生变化。当单个的查询对数据库服务器造成比较大的消耗时,并发数越大,系统的性能下降越厉害,并且带来一系列互相关联又互相影响的问题,阻塞严重,死锁概率变大。对稍复杂的系统,出现这种情况时,在系统层面做优化有可能是件无法实施的事情,因此不少的系统会选择将过期的数据导出。
&&& 另外效率方面是指,如果这个查询结果比较多,绝大部分情况下对于用户而言这是一个无效查询,因为用户不大可能在大量数据里面再去检索他想要的数据。面对这种情况,用户极有可能会凭记忆,查文档查邮件等其它方式找到一些更精确的条件,缩小查询结果的范围。有的人会提出,结果集是按特定字段排序的,用户拉动滚动条、翻页等可能会比较方便的找到他想要的数据。对于CS下面的产品,这的确是有可能,在BS上,我们不可能一个页面显示太多数据,翻页去找用户一般是不大愿意的,并且,对于那些被用户忽略的数据,我们还是白白耗费了服务器资源,是否有更好的方式避免这种情况呢?另外有的人可能会提出,添加一个类似SQL Server里面的Top语句,或者使用Oracle的Rowid限制结果集数量。其实这种做法在上面示例的情况下,除了减少网络传输方面比较明显,在数据库查询方面并不能带来多少效率的提高,因为数据库仍然得完成Join等操作之后,才能知道Top或者是Rowid所指定的范围,你从执行计划里面都可以发现这一点。这种低效的查询,耗费服务器资源做无用功,当你看着服务器内存、CPU、I/O消耗很高,而执行的查询有效性却比较低时,作为架构师、设计师的你,会有什么感想?
&&& 其实,过于复杂SQL的出现,复杂SQL所带来的系统性能问题,本身就直接意味着系统架构、分析设计、开发方面的问题。
&&& 象上面提到的那个系统,不使用一个JOIN,大家可能会觉得它走了一个极端。它是通过在系统分析、设计上下足功夫来实现的,在下面了解它的做法之后,我们能够知道,它的做法增加了一些与数据库的交互,在用户多的情况下并发情况可能会稍严重一些,但跟上面的分析相比较,这绝对是不足以提的。并且这种情况下遇到的数据库性能问题,我们可以认为是一种正常的良性的状态,是一种必然,通过提高服务器配置,调整数据库服务器架构等,能够比较容易和明显的改善,而不用调整系统。
&&& 一般的做法下,使用JOIN方式,界面差不多是这个样子,这也是很直接很自然的一种方法:
&&& 为了避免使用JOIN,界面就只允许按照客户编码的字段进行查询了,界面的样子如下:
&&& 当用户需要根据其他客户资料查询销售订单时,将这个操作拆分成两个步骤。第一步根据客户的其他资料先准确查询到这个客户,这个步骤通过Customer NO.查询条件输入框后面的图标按钮,弹出一个详细的客户资料查询界面。当准确查询到某个客户资料并确定之后,弹出的客户查询界面将客户编码返回,填入到Customer NO.查询条件输入框中。第二步就是根据返回的客户编码查询销售订单。
&&& 当然你可能会想说,这种做法用户使用起来不方便,因为只有一个客户编码的输入框。作为系统分析设计人员,需要能敏锐地觉察到这种问题,一个订单查询的用例,经过这样分拆之后,可能的确给用户的操作带来局限性,所以需要进一步思考完善。看一下下面的界面:
&&& 首先,Customer NO.输入框有多组,用户可以输入多个值进行查询;在第三行Customer NO.输入框位置有一个向下的图标按钮,点击之后可以动态添加一组Customer NO.输入框。其次一般的系统,象这种客户编码、销售订单编号等,都是有规则的,相同的前缀可能意味着属于同一个类别,因此每一组Customer NO.条件都使用From、To的查询方式,就是说那些 From值 =& Customer NO. =& To值。另外,假如你有一个Excel文件,里面有10个客户编码的列表,现在你需要查询这10个客户某个月份的销售订单,一个一个输入可能还是比较麻烦,这种情况下可以提供一个上传查询条件列表文件的方式,避免用户频繁输入。
&&& 说明一下,参考的系统是CS结构,在BS上实现可能有些繁琐复杂,我们不去探究这些方面,而是把它当作一种分析设计方法参考。
&&& 从效率和性能方面分析一下。将原本需要JOIN完成的查询拆分成了两个步骤进行,每一步都是进行一个单表查询。在单表查询的基础上做性能方面的考虑,这个问题就变得非常单纯,容易很多,例如索引的建立和使用,使用Top、Rowid等方式提高数据库的使用效率和查询性能(默认返回500、200条记录,用户可以输入具体的数字到底返回多少条)等。如果你对数据库的理解比较透彻,用前面提到的SalesOrder和Customer都是几十万、几百万数据量的情况做一下对比,如果再加上Top、Rowid等方式的使用,用户完成同样的一个任务,数据库的开销可能是1个甚至几个数量级的改善。
&&& 在系统开发方面,简洁、清晰。一个客户查询对话框,可以在所有类似使用客户资料进行查询的地方使用。数据列表的显示只是基于单个对象,有利于做成通用的控件、组件方式,比较自然的嵌入在整体技术架构中。如果查询条件部分也能够控件化,你的系统界面开发会是一个全新的面貌。
&&& 在架构设计方面的优点,大家可以想到的可能更多。原本一个用例需要结合两个对象一起考虑,现在每一步骤只是针对一个对象操作,这样有利于业务对象逻辑的完整封装。对复杂关联关系的考虑,造成技术框架方方面面的复杂性,随着这个拆解消失。例如ORM,如果不需要关联关系,不需要HQL,根本不需要考虑开发者会自己定义一个查询,返回一个比较随意的DataSet而不是Domain Model Object,你对ORM的开发、选择还会有什么考虑?
&&& 复杂的系统逻辑,并不会因为上面这样一个简简单单的方案就能解决所有的JOIN问题,不同的场景下你需要进行不同的分析,例如有的情况下适当的添加冗余表、冗余字段等。
&&& 啰嗦的写了一大段,并不是想告诉大家这里有这样一个解决JOIN的方法,大家以后不要用JOIN了。而只是想结合在这样一个示例中说明一下业务分析、设计的重要性。很多时候我们太过依赖于技术而忽视业务分析设计,而去追求一组互相矛盾的目标:开发维护高效方便、系统灵活扩展性伸缩性良好等等,因此我们开始进入一个漫漫的旅程,对技术的要求越来越高,对框架的要求越来越多也越复杂,而你却始终都发现还是难以接近目标。如果你在业务分析设计上多花一些精力,可能取得的效果会完全不一样。
&&& 最后祝大家新年快乐!随笔 - 32&
文章 - 52&
评论 - 344&
&&&&&&&&&&&
  数据库设计,一个软件项目成功的基石。很多从业人员都认为,数据库设计其实不那么重要。现实中的情景也相当雷同,开发人员的数量是数据库设计人员的数倍。多数人使用数据库中的一部分,所以也会把数据库设计想的如此简单。其实不然,数据库设计也是门学问。
  从笔者的经历看来,笔者更赞成在项目早期由开发者进行数据库设计(后期调优需要DBA)。根据笔者的项目经验,一个精通OOP和ORM的开发者,设计的数据库往往更为合理,更能适应需求的变化,如果追其原因,笔者个人猜测是因为数据库的规范化,与OO的部分思想雷同(如内聚)。而DBA,设计的数据库的优势是能将DBMS的能力发挥到极致,能够使用SQL和DBMS实现很多程序实现的逻辑,与开发者相比,DBA优化过的数据库更为高效和稳定。如标题所示,本文旨在分享一名开发者的数据库设计经验,并不涉及复杂的SQL语句或DBMS使用,因此也不会局限到某种DBMS产品上。真切地希望这篇文章对开发者能有所帮助,也希望读者能帮助笔者查漏补缺。
一&Codd的RDBMS12法则&&RDBMS的起源
  Edgar Frank Codd(埃德加&弗兰克&科德)被誉为&关系数据库之父&,并因为在数据库管理系统的理论和实践方面的杰出贡献于1981年获图灵奖。在1985年,Codd 博士发布了12条规则,这些规则简明的定义出一个关系型数据库的理念,它们被作为所有关系数据库系统的设计指导性方针。
信息法则 关系数据库中的所有信息都用唯一的一种方式表示&&表中的值。
保证访问法则 依靠表名、主键值和列名的组合,保证能访问每个数据项。
空值的系统化处理 支持空值(NULL),以系统化的方式处理空值,空值不依赖于数据类型。
基于关系模型的动态联机目录 数据库的描述应该是自描述的,在逻辑级别上和普通数据采用同样的表示方式,即数据库必须含有描述该数据库结构的系统表或者数据库描述信息应该包含在用户可以访问的表中。
统一的数据子语言法则 一个关系数据库系统可以支持几种语言和多种终端使用方式,但必须至少有一种语言,它的语句能够一某种定义良好的语法表示为字符串,并能全面地支持以下所有规则:数据定义、视图定义、数据操作、约束、授权以及事务。(这种语言就是SQL)
视图更新法则 所有理论上可以更新的视图也可以由系统更新。
高级的插入、更新和删除操作 把一个基础关系或派生关系作为单个操作对象处理的能力不仅适应于数据的检索,还适用于数据的插入、修改个删除,即在插入、修改和删除操作中数据行被视作集合。
数据的物理独立性 不管数据库的数据在存储表示或访问方式上怎么变化,应用程序和终端活动都保持着逻辑上的不变性。
数据的逻辑独立性 当对表做了理论上不会损害信息的改变时,应用程序和终端活动都会保持逻辑上的不变性。
数据完整性的独立性 专用于某个关系型数据库的完整性约束必须可以用关系数据库子语言定义,而且可以存储在数据目录中,而非程序中。
分布独立性 不管数据在物理是否分布式存储,或者任何时候改变分布策略,RDBMS的数据操纵子语言必须能使应用程序和终端活动保持逻辑上的不变性。
非破坏性法则 如果一个关系数据库系统支持某种低级(一次处理单个记录)语言,那么这个低级语言不能违反或绕过更高级语言(一次处理多个记录)规定的完整性法则或约束,即用户不能以任何方式违反数据库的约束。
二&关系型数据库设计阶段
(一)规划阶段
  规划阶段的主要工作是对数据库的必要性和可行性进行分析。确定是否需要使用数据库,使用哪种类型的数据库,使用哪个数据库产品。
(二)概念阶段
  概念阶段的主要工作是收集并分析需求。识别需求,主要是识别数据实体和业务规则。对于一个系统来说,数据库的主要包括业务数据和非业务数据,而业务数据的定义,则依赖于在此阶段对用户需求的分析。需要尽量识别业务实体和业务规则,对系统的整体有初步的认识,并理解数据的流动过程。理论上,该阶段将参考或产出多种文档,比如&用例图&,&数据流图&以及其他一些项目文档。如果能够在该阶段产出这些成果,无疑将会对后期进行莫大的帮助。当然,很多文档已超出数据库设计者的考虑范围。而且,如果你并不精通该领域以及用户的业务,那么请放弃自己独立完成用户需求分析的想法。用户并不是技术专家,而当你自身不能扮演&业务顾问&的角色时,请你选择与项目组的相关人员合作,或者将其视为风险呈报给PM。再次强调,大多数情况,用户只是行业从业者,而非职业技术人员,我们仅仅从用户那里收集需求,而非依赖于用户的知识。
  记录用户需求时,可以使用一些技巧,当然这部分内容有些可能会超出数据库设计人员的职责:
努力维护一系列包含了系统设计和规格说明信息的文档,如会议记录、访谈记录、关键用户期望、功能规格、技术规格、测试规格等。
频繁与干系人沟通并收集反馈。
标记出你自己添加的,不属于客户要求的,未决内容。
与所有关键干系人尽快确认项目范围,并力求冻结需求。
  此外,必须严谨处理业务规则,并详细记录。在之后的阶段,将会根据这些业务规则进行设计。
  当该阶段结束时,你应该能够回答以下问题:
需要哪些数据?
数据该被怎样使用?
哪些规则控制着数据的使用?
谁会使用何种数据?
客户想在核心功能界面或者报表上看到哪些内容?
数据现在在哪里?
数据是否与其他系统有交互、集成或同步?
主题数据有哪些?
核心数据价值几何,对可靠性的要求程度?
  并且得到如下信息:
实体和关系
可以在数据库中强制执行的业务规则
需要使用数据库的业务过程
(三)逻辑阶段
  逻辑阶段的主要工作是绘制E-R图,或者说是建模。建模工具很多,有不同的图形表示方法和软件。这些工具和软件的使用并非关键,笔者也不建议读者花大量时间在建模方法的选择上。对于大多数应用来说,E-R图足以描述实体间的关系。建模关键是思想而不是工具,软件只是起到辅助作用,识别实体关系才是本阶段的重点。
  除了实体关系,我们还应该考虑属性的域(值类型、范围、约束)
(四)实现阶段
  实现阶段主要针对选择的RDBMS定义E-R图对应的表,考虑属性类型和范围以及约束。
(五)物理阶段
  物理阶段是一个验证并调优的阶段,是在实际物理设备上部署数据库,并进行测试和调优。
三 设计原则
(一)降低对数据库功能的依赖
  功能应该由程序实现,而非DB实现。原因在于,如果功能由DB实现时,一旦更换的DBMS不如之前的系统强大,不能实现某些功能,这时我们将不得不去修改代码。所以,为了杜绝此类情况的发生,功能应该有程序实现,数据库仅仅负责数据的存储,以达到最低的耦合。
(二)定义实体关系的原则
  当定义一个实体与其他实体之间的关系时,需要考量如下:
牵涉到的实体 识别出关系所涉及的所有实体。
所有权 考虑一个实体&拥有&另一个实体的情况。
基数 考量一个实体的实例和另一个实体实例关联的数量。
  关系与表数量
描述1:1关系最少需要1张表。
描述1:n关系最少需要2张表。
描述n:n关系最少需要3张表。
(三)列意味着唯一的值
  如果表示坐标(0,0),应该使用两列表示,而不是将&0,0&放在1个列中。
(四)列的顺序
  列的顺序对于表来说无关紧要,但是从习惯上来说,采用&主键+外键+实体数据+非实体数据&这样的顺序对列进行排序显然能得到比较好的可读性。
(五)定义主键和外键
  数据表必须定义主键和外键(如果有外键)。定义主键和外键不仅是RDBMS的要求,同时也是开发的要求。几乎所有的代码生成器都需要这些信息来生成常用方法的代码(包括SQL文和引用),所以,定义主键和外键在开发阶段是必须的。之所以说在开发阶段是必须的是因为,有不少团队出于性能考虑会在进行大量测试后,在保证参照完整性不会出现大的缺陷后,会删除掉DB的所有外键,以达到最优性能。笔者认为,在性能没有出现问题时应该保留外键,而即便性能真的出现问题,也应该对SQL文进行优化,而非放弃外键约束。
(六)选择键
1 人工键与自然键
  人工健&&实体的非自然属性,根据需要由人强加的,如GUID,其对实体毫无意义;自然健&&实体的自然属性,如身份证编号。
  人工键的好处:
键值永远不变
永远是单列存储
  人工键的缺点:
因为人工键是没有实际意义的唯一值,所以不能通过人工键来避免重复行。
  笔者建议全部使用人工键。原因如下:
在设计阶段我们无法预测到代码真正需要的值,所以干脆放弃猜测键,而使用人工键。
人工键复杂处理实体关系,而不负责任何属性描述,这样的设计使得实体关系与实体内容得到高度解耦,这样做的设计思路更加清晰。
  笔者的另一个建议是&&每张表都需要有一个对用户而言有意义的自然键,在特殊情况下也许找不到这样一个项,此时可以使用复合键。这个键我在程序中并不会使用其作为唯一标识,但是却可以在对数据库直接进行查询时使用。
  使用人工键的另一根弊端,主要源自对查询性能的考量,因此选择人工键的形式(列的类型)很重要:
自增值类型 由于类型轻巧查询效率更好,但取值有限。
GUID 查询效率不如值类型,但是取值无限,且对开发人员更加亲切。
2 智能健与非智能键
  智能键&&键值包含额外信息,其根据某种约定好的编码规范进行编码,从键值本身可以获取某些信息;非智能键,单纯的无意义键值,如自增的数字或GUID。
  智能键是一把双刃剑,开发人员偏爱这种包含信息的键值,程序盼望着其中潜在的数据;数据库管理员或者设计者则讨厌这种智能键,原因也是很显然的,智能键对数据库是潜在的风险。前面提到,数据库设计的原则之一是不要把具有独立意义的值的组合实现到一个单一的列中,应该使用多个独立的列。数据库设计者,更希望开发人员通过拼接多个列来得到智能键,即以复合主键的形式给开发人员使用,而不是将一个列的值分解后使用。开发人员应该接受这种数据库设计,但是很多开发者却想不明白两者的优略。笔者认为,使用单一列实现智能键存在这样一个风险,就是我们可能在设计阶段无法预期到编码规则可能会在后期发生变化。比如,构成智能键的局部键的值用完而引起规则变化或者长度变化,这种编码规则的变化对于程序的有效性验证与智能键解析是破坏性的,这是系统运维人员最不希望看到的。所以笔者建议如果需要智能键,请在业务逻辑层封装(使用只读属性),不要再持久化层实现,以避免上述问题。
(七)是否允许NULL
  关于NULL我们需要了解它的几个特性:
任何值和NULL拼接后都为NULL。
所有与NULL进行的数学操作都返回NULL。
引入NULL后,逻辑不易处理。
  那么我们是否应该允许列为空呢?笔者认为这个问题的答案受到我们的开发语言的影响。以C#为例,因为引入了可空类型来处理数据库值类型为NULL的情形,所以是否允许为空对开发者来说意义并不大。但有一点必须注意,就是验证非空必须要在程序集进行处理,而不该依赖于DBMS的非空约束,必须确保完整数据(所有必须的属性均被赋值)到达DB(所谓的&安全区&,我们必须定义在多层系统中那些区域得到的数据是安全而纯净的)。
(八)属性切割
  一种错误想法是,属性与列是1:1的关系。对于开发者,我们公开属性而非字段。举个例子来说,对于实体&员工&有&名字&这一属性,&名字&可以再被分解为&姓&和&名&,对于开发人员来说,显然第二种数据结构更受青睐(&姓&和&名&作为两个字段)。所以,在设计时我们也应该根据需要考虑是否切割属性。
(九)规范化&&范式
  当笔者还在大学时,范式是学习关系型数据库时最头疼的问题。我想也许会有读者仍然不理解范式的价值,简单来说&&范式将帮助我们来保证数据的有效性和完整性。规范化的目的如下:
消灭重复数据。
避免编写不必要的,用来使重复数据同步的代码。
保持表的瘦身,以及减从一张表中读取数据时需要进行的读操作数量。
最大化聚集索引的使用,从而可以进行更优化的数据访问和联结。
减少每张表使用的索引数量,因为维护索引的成本很高。
  规范化旨在&&挑出复杂的实体,从中抽取出简单的实体。这个过程一直持续下去,直到数据库中每个表都只代表一件事物,并且表中每个描述的都是这件事物为止。
1 规范化实体和属性(去除冗余)
  1NF:每个属性都只应表示一个单一的值,而非多个值。
  需要考虑几点:
属性是原子性的 需要考虑熟悉是否分解的足够彻底,使得每个属性都表示一个单一的值。(和&(三)列意味着唯一的值&描述的原则相同。)分解原则为&&当你需要分开处理每个部分时才分解值,并且分解到足够用就行。(即使当前不需要彻底分解属性,也应该考虑未来可能的需求变更。)
属性的所有实例必须包含相同数量的值 实体有固定数量的属性(表有固定数量的列)。设计实体时,要让每个属性只有固定数量的值与其相关联。
实体中出现的所有实体类型都必须不同
  当前设计不符合1NF的&臭味&:
包含分隔符类字符的字符串数据。
名字尾端有数字的属性。
没有定义键或键定义不好的表。
2 属性间的关系(去除冗余)
  2NF-实体必须符合1NF,每个属性描述的东西都必须针对整个键(可以理解为oop中类型属性的内聚性)。
  当前设计不符合2NF的&臭味&:
重复的键属性名字前缀(设计之外的数据冗余) 表明这些值可能描述了某些额外的实体。
有重复的数据组(设计之外的数据冗余) 这标志着属性间有函数依赖型。
没有外键的复合主键 这标志着键中的键值可能标识了多种事物,而不是一种事物。
  3NF-实体必须符合2NF,非键属性不能描述其他非键属性。(与2NF不同,3NF处理的是非键属性和非键属性之间的关系,而不是和键属性之间的关系。
  当前设计不符合3NF的&臭味&:
多个属性有同样的前缀。
重复的数据组。
汇总的数据,所引用的数据在一个完全不同的实体中。(有些人倾向于使用视图,我更倾向于使用对象集合,即由程序来完成。)
  BCNF-实体满足第一范式,所有属性完全依赖于某个键,如果所有的判定都是一个键,则实体满足BCNF。(BCNF简单地扩展了以前的范式,它说的是:一个实体可能有若干个键,所有属性都必须依赖于这些键中的一个,也可以理解为&每个键必须唯一标识实体,每个非键熟悉必须描述实体。&
3 去除实体组合键中的冗余
  4NF-实体必须满足BCNF,在一个属性与实体的键之间,多值依赖(一条记录在整个表的唯一性由多个值组合起来决定的)不能超过一个。
  当前设计不符合4NF的&臭味&:
三元关系(实体:实体:实体)。
潜伏的多值属性。(如多个手机号。)
临时数据或历史值。(需要将历史数据的主体提出,否则将存在大量冗余。)
4 尽量将所有关系分解为二元关系 
  5NF-实体必须满足4NF,当分解的信息无损的时候,确保所有关系都被分解为二元关系。
  5NF保证在第四范式中存在的任何可以分解为实体的三元关系都被分解。有的三元关系可以在不丢失信息的前提下被分解为二元关系,当分解为两个二元关系的过程要丢失信息时,关系被宣称为处于第四范式中。所以,第五范式建议是,最好把现有的三元关系都分解为3个二元关系。
  需要注意的是,规范化的结果可能是更多的表,更复杂的查询。因此,处理到何种程度,取决于性能和数据架构的多方考量。建议规范化到第四范式,原因是5NF的判断太过隐晦。例如:表X(老师,学生,课程)是一个三元关系,可以分解为表A(老师,学生),表B(学生,课程),表C(老师,课程)。表X表示某个老师是上某个学生的某个课程的老师;表A表示老师教学生;表B表示学生上课;表C表示老师教课。单独看是无法发现问题的,但是从数据出发,"表X=表A+表B+表C"并不一定成立,即不能通过连接构建分解前的数据。因为可能有多种组合,丧失了表X反馈出的业务规则。这种现象,容易在设计阶段被忽略,但好在在开放阶段会被显现,而且并不经常发生。
  推荐做法:
尽可能地遵守上述规范化原则。
所有属性描述的都应该是体现被建模实体的本质的内容。
至少必须有一个键,它唯一地标识和描述了所建实体的本质。
主键要谨慎选择。
在逻辑阶段能做多少规范化就做多少(性能不是逻辑阶段考虑的范畴)。
(十)选择数据类型(MS SQL 2008)
  MS SQL的常用类型:
不会发生精度损失
bit tinyint smallint int bigint decimal
对于极值可能发生精度损失
float(N) real
日期和时间
date time smalldatetime datetime datetime2 datetimeoffset
二进制数据
bingary(N) varbinary(N) varbinary(max)
字符(串)数据
char(N) varchar(N) varchar(max) nchar(N) nvarchar(N) nvarchar(max)
存储任意数据
sql_variant
uniqueidentifier
不要试图使用该类型规避1NF
geometry geography
heirarchyid
  MS SQL中不在支持的或糟糕的类型选择
image:被varbinary(max)取代。
text和ntext:被varchar(max)和nvarchar(max)取代。
money和smallmoney:开发过程中不好用,建议使用decimal。
  常用类型选择:
  类型选择的最基本规则是选择满足需要的最轻的类型,因为这样查询更快。
建议使用bit而非char(1),因为开发语言对其支持觉好,可以直接映射为bool或bool?。
使用所有备选类型中最小的那种,类型越大,查询越慢,当字节大于8000时,应使用max。
自增主键根据预期范围选择int或bigint,GUID使用uniqueidentifier而非varchar(N)。
(十一)优化并行
  设计DB时就应该考虑到对并行进行优化,比如,MS SQL中的timestamp类型就是极好的选择。
四 命名规则
表&&&模块名_表名&。表名最好不要用复数,原因是在使用ORM框架开发时,代码生成器根据DB生成类定义,表生成了某个实例的类型定义,而不是实例集合。表名不要太长。原因之一,某些软件对表名最大长度有限制;原因之二,使用代码生成器往往会根据表名生产类型名称,之后懒人会直接使用这一名称,如果将太长的名称跨网络边界显然不是明智之举。
字段&&bool类型用&Is&、&Can&、&Has&等表示;日期类型命名必须包含&Date&;时间类型必须包含&Time&。
存储过程&&使用&proc_&前缀。
视图&&使用&view_&前缀。
触发器&&使用&trig_&前缀。
阅读(...) 评论()

我要回帖

更多关于 数据库设计说明书 的文章

 

随机推荐