数据库主键/外键设计之思考

来源:互联网 发布:英语写作课程知乎 编辑:程序博客网 时间:2024/06/06 00:49

1.是否要采用GUID作为主键

用GUID作主键有它的优势与不足.优势是GUID具有唯一性,在任何情况下,可以产生全球唯一的值.这是GUID最大的优势,也方便数据导入,比如要求从另一个系统中把数据导入进来,那么,不用担心,导入时,会导致主键冲突.不足是GUID值太复杂.不易记忆,因为有时,难免我们会用记录的方式,来进行记录判断.而且数据太长,影响数据库效率.GUID的产生不是以一定的次序产生,对于按主键物理排序的数据库来说,如果在记录的前部插入一条记录,可能会导致后面N次方的数据条数后移.这将导致数据插入效率.因此GUID的采用应该要慎重.

2.是否要采用自动递增的方式

对于以前谈到的主键,要求唯一性,因此大家都用自动递增的方式.这样的方式是非常不可取的.可能是为了方便插入记录时,不必去人为创建主键值.以为这样会方便,其实不是的.带来的麻烦要远远胜于这种所谓的"方便".第一:数据导入不方便,经常会有从另一系统导入数据进来,自动递增的主键,将不允许原表中的ID被导入进来.这会导致主键丢失.第二:对于象订单这样的有主外键的表来说,如果订单的"主档表"主键是自动生成的.那么在保存一个订单时,会要求对主档表与明细表同进行事务保存,而此时,先要生成一条订单,然后取出这个订单自动生成的主键,然后再把此作为明细表的一个外键,进行明细的保存.这过程中,将变以复杂而且不可行.事务如何处理.订单主档表插入记录后,要是明细保存时遇到错误,主档表记录还要进行删除.烦.插入成功以后,还要取出产生的最大值.这将是一个严重的浪费.记录多的话会影响速度,而且会存在并行插入.导致获取的记录可能是不正确的. 因此在以上的严重问题下,请不要采用自动递增方式.

3.是否要采用int型作为主键

以前大家都采用int型,都是出来主键都是数字导致的.其实我们也明白.并不是只是数字的东西就是数字型的.比如电话号码等.因此对于主键,采用int型的优势是速度快,插入,查询时都可能会比其他的方式快.但我这种快的效果也未必有多明显,比如以varchar(15)为例,物理主键排序的数据,会自动以主键进行物理数据排序.因此,就算是字符型的数据,在插入时也会插入到相应的物理位置上,也就是说,在插入时可能会影响一些速度.但在以后的查询中,速度影响不会太明显.而我要说的,不采用int型作为主键,不是说,里面不存数据.我还是建议大家在主键中存放数字,这样的排序比较要比夹杂字母的排序来的快,之所以要采用字符型,也是为以后的数据导入作准备,有一天,会要求从其他表导入数据时,可以在导入数据的主键上加一个特定字母来避免与原主键冲突.比如在导入数据的主键前加一个"N"字母.这也就不用担心,要求导入数据表中的主键是数字型还是字符型了.

4.是否采用编号来定义主键

这个问题是老生常谈了.主键设计有个原则,就是主键不应具有任何实际意义.这条其实是非常重要,有人就是觉得编号本身是唯一的,可以作为主键用,但可能会为以后带来麻烦.因为带有实际意义的字段,还是存在被修改的可能性.而对于主键最大的忌讳就是修改主键,这可能会导致非常严重的不可估计的后果.比如学生编号,平时以为永远不会修改,但修改的可能还是会存在.

还有一种,表面上是唯一的,但实际上应该是允许重复的.我举个例子,订单吧,订单编号应该是唯一吧.是的.可是会存在这样的情况,一张原来的订单是因为某个原因,要求订单作废.那好给订单的状态标识为"cancel".然后允许再次录入同样编号的订单.因此.对于这样的情况下在,虽然有效的订单编号只有一个,但在数据库角度会允许编号重复.所以不管如何,还是建议大家为表都建一个没有任何意义的主键,如ID.

因此,总结一下,在设计主键,会采用字符型的.不采用自动递增,在新增记录时,系统生成主键值.一般为全数字进行存入,至于主键值的生成规则,可以按需求进行规则定义.如果没有特殊的要求,只是为了保持唯一,可以定义一个字段存放一个数值.在生成时,自动加一.然后再存回去.这也比从一个表中寻找最大值要来的快吧.

数据库主键设计之思考二
主键的必要性:

有些朋友可能不提倡数据库表必须要主键,但在我的思考中,觉得每个表都应
该具有主键,不管是单主键还是双主键,主键的存在就代表着表结构的完整性,表
的记录必须得有唯一区分的字段,主键主要是用于其他表的外键关联,本记录的修
改与删除,当我们没有主键时,这些操作会变的非常麻烦。

主键的无意义性:

我强调主键不应该具有实际的意义,这可能对于一些朋友来说不太认同,比如
订单表吧,会有"订单编号"字段,而这个字段呢在业务实际中本身就是应该具有
唯一性,具有唯一标识记录的功能,但我是不推荐采用订单编号字段作为主键的,
因为具有实际意义的字段,具有"意义更改"的可能性,比如订单编号在刚开始的
时候我们一切顺利,后来客户说"订单可以作废,并重新生成订单,而且订单号要
保持原订单号一致",这样原来的主键就面临危险了。因此,具有唯一性的实际字
段也代表可以作为主键。因此,我推荐是新设一个字段专门用为主键,此主键本身
在业务逻辑上不体现,不具有实际意义。而这种主键在一定程序增加了复杂度,所
以要视实际系统的规模大小而定,对于小项目,以后扩展不会很大的话,也查允许
用实际唯一的字段作主键的。

主键的选择

我们现在在思考一下,应该采用什么来作表的主键比较合理,申明一下,主键
的设计没有一个定论,各人有各人的方法,哪怕同一个,在不同的项目中,也会采
用不同的主键设计原则。

第一:编号作主键

此方法就是采用实际业务中的唯一字段的"编号"作为主键设计,这在小型的
项目中是推荐这样做的,因为这可以使项目比较简单化,但在使用中却可能带来一
些麻烦,比如要进行"编号修改"时,可能要涉及到很多相关联的其他表,就象黎
叔说的"后果很严重";还有就是上面提到的"业务要求允许编号重复时",我们再
那么先知,都无法知道业务将会修改成什么?

第二:自动编号主键

这种方法也是很多朋友在使用的,就是新建一个ID字段,自动增长,非常方
便也满足主键的原则,优点是:数据库自动编号,速度快,而且是增量增长,聚集
型主键按顺序存放,对于检索非常有利;数字型的,占用空间小,易排序,在程序中
传递也方便;如果通过非系统增加记录(比如手动录入,或是用其他工具直接在表里
插入新记录,或老系统数据导入)时,非常方便,不用担心主键重复问题。

缺点:其实缺点也就是来自其优点,就是因为自动增长,在手动要插入指定
ID的记录时会显得麻烦,尤其是当系统与其他系统集成时,需要数据导入时,很难
保证原系统的ID不发生主键冲突(前提是老系统也是数字型的);如果其他系统主键
不是数字型那就麻烦更大了,会导致修改主键数据类型了,这也会导致其他相关表
的修改,后果同样很严重;就算其他系统也是数字型的,在导入时,为了区分新老数
据,可能想在老数据主键前统一加一个"o"(old)来表示这是老数据,那么自动增
长的数字型又面临一个挑战。

第三:Max加一

由于自动编号存在那些问题,所以有些朋友就采用自己生成,同样是数字型的
,只是把自动增长去掉了,采用在Insert时,读取Max值后加一,这种方法可以避免
自动编号的问题,但也存在一个效率问题,如果记录非常大的话,那么Max()也会影
响效率的;更严重的是并发性问题,如果同时有两人读到相同的Max后,加一后插入
的ID值会重复,这已经是有经验教训的了。

第四:自制加一

考虑Max加一的效率后,有人采用自制加一,也就是建一个特别的表,字段为
:表名,当前序列值。这样在往表中插入值时,先从此表中找到相应表的最大值后
加一,进行插入,有人可能发现,也可能会存在并发处理,这个并发处理,我们可
以采用lock线程的方式来避免,在生成此值的时,先Lock,取到值以后,再unLock
出来,这样不会有两人同时生成了。这比Max加一的速度要快多了。但同样存在一个
问题:在与其他系统集成时,脱离了系统中的生成方法后,很麻烦保证自制表中的
最大值与导入后的保持一致,而且数字型都存在上面讲到的"o"老数据的导入问题
。因此在"自制加一"中可以把主键设为字符型的。字符型的自制加一我倒是蛮推
荐的,应该字符型主键可以应付很多我们意想不到的情况。

第五:GUID主键

目前一个比较好的主键是采用GUID,当然我是推荐主键还是字符型的,但值由
GUID生成,GUID是可以自动生成,也可以程序生成,而且键值不可能重复,可以解
决系统集成问题,几个系统的GUID值导到一起时,也不会发生重复,就算有"o"老
数据也可以区分,而且效率很高,在.NET里可以直接使用System.Guid.NewGuid()进
行生成,在SQL里也可以使用 NewID()生成。优点是:

同 IDENTITY 列相比,uniqueidentifier 列可以通过 NewID() 函数提前得知
新增加的行 ID,为应用程序的后续处理提供了很大方便。

便于数据库移植,其它数据库中并不一定具有 IDENTITY 列,而 Guid 列可以
作为字符型列转换到其它数据库中,同时将应用程序中产生的 GUID 值存入数据库
,它不会对原有数据带来影响。

便于数据库初始化,如果应用程序要加载一些初始数据, IDENTITY 列的处理
方式就比较麻烦,而 uniqueidentifier 列则无需任何处理,直接用 T-SQL 加载即
可。

便于对某些对象或常量进行永久标识,如类的 ClassID,对象的实例标识,UD
DI 中的联系人、服务接口、tModel标识定义等。

缺点是:

GUID 值较长,不容易记忆和输入,而且这个值是随机、无顺序的。

GUID 的值有 16 个字节,与其它那些诸如 4 字节的整数相比要相对大一些。
这意味着如果在数据库中使用 uniqueidentifier 键,可能会带来两方面的消极影
响:存储空间增大;索引时间较慢。

我也不是推荐GUID最好,其实在不同的情况,我们都可以采用上面的某一种方
式,思考了一些利与弊,也方便大家在进行设计时参考。这些也只是我的一点思考
而已,而且可能我知识面限制,会有一些误论在里面,希望大家有什么想法欢迎讨
论。

数据库外键设计之思考
外键的目的:通过数据库去保证数据的完整性,提高关联查询的效率
副作用:插入或更新的效率下降。

1。要用:
通过数据库保证数据完成性。两个表关联查询很多的时候,并且数据量很大,可以不考虑插入或更新的效率时候。
2。可用,可以不用
可以忍受数据不完整性。或认为程序已经可以控制数据的完整性。
两个表关联查询很多的时候,但两个表的数据量不大或两个表关联查询很多,对查询效率不高,对插入或更新的效率要求高,
3。不要
两个表无任何逻辑关系,对插入或更新效率要求及高


--------------------------------------------------------------------------------
根据多年设计经验,我总结了一些应用系统设计套路和大家一起探讨探讨,比较简单,抛转引玉而已

数据库部分:

每次来一个新的系统,做完需求后,总是要设计数据库, 根据业务需求设计表结构,也是有一定套数的,如果不按照这些套数走,总是会系统实施,维护带来问题,教材上的三个范式比较抽象, 我这几年总结下来,觉得数据库设计主要就下面几个类型

两大主要类型,维护类和应用类

维护类有3种类型

代码表 程序里用来进行硬编码的表,这类表一般都可以通过程序里定义枚举,集合类型来代替 常见的设计是id,code,name三字段,这些表在系统初始化的时候,不可以清除数据

基础表 一个系统里面的基础资料,比如职工,科室,部门这些,这类表还有一个特性就是需要分类.常见设计是两个表,表一是实体表,必须的字段是ID,编码code,名称name,备注,删除标志,所在分类 表二是分类表,作用是对实体表进行分类,字段有id,上级编码,是否末级,name,两个表通过表一的所在分类和表二的id进行关联.

基础表最典型的应用就是部门表的设计,部门是个树状结构,设计表结构的时候,新手总爱设计成一个表,带来很多问题,按照基础表模式设计,就可以避免

关系对照表 对两个基础表进行组合, ,其字段有ID,基础表一主键id,基础表二主键id. 典型应用是:要保存职工和部门的关系,设计的时候并不是在职工表里面添个所属部门来关联,而是单独设计一个职工部门关系对照表

应用表有两大类型,就是大家比较熟悉的主从明细表类型

主表 根据需求来设计,这类表需要的字段除了业务信息外,还需要审核标志,制单人,审核人,操作时间,以及业务流程控制字段

从表 保存主表的详细业务信息,通过主表的ID做关联,往往是一条主表记录关联多条从表记录

应用表典型应用就是管理系统中各种单据的保存

 

关于ID的设计,有两种选择比较好

第一个是 自增长的ID,整形数

第二个是 GUID类型,(在分布式开发时候,推荐使用)

 

在设计一个应用系统的数据库时候,强烈建议先做概念模型,每个表大多数都可以按照上面总结的类型来设计

表的设计规范化后,最大的好处还在于,在开发程序中,针对这五种类型,配合对象持久化技术,就可以开发通用的程序模板,从而提高开发效率

 

 

原创粉丝点击