最佳数据库设计(转)

最佳数据库设计(转) 

1,主键 
自增长主键,如ORACLE的序列或是UUID算法或是自己制作一个生成唯一数字的类,主键无意义性。 
避免用复合主键。 
双主键,即一个无意义的ID字段作主键,如用序列。另一个则是业务编号NO作为主键(如产品号,用户代码等。)因为如ORACEL只能有一个主键,这个则是一个UNIQUE KEY。在不能完全确定业务NO的意义时,定义成VARCHAR2,当然这可能影响性能。 

对于业务编号NO,不作为主键,主要有如下原因:用一个序列“数字型”作为主键,性能更好。二是对于业务编码可能存在编码规则在运行后期会有变化,以及长度估计不准等。特别比如用身份证作为主键就有很多问题,比如在以前15时都是数字,就设计成NUMBER(15),可是当增加到18位时,长度不够,更糟糕的是18位身份请中可能存在字母。数据类型改变是一个更大的问题。又如,对于定单,订单编号在刚开始的时候我们一切顺利,后来客户说“订单可以作废,并重新生成订单,而且订单号要保持原订单号一致”,这样原来的主键就面临危险了。 
双主键还有一个借助操作系统的设计,删除一个PETER的用户,后来又来一个PETER,则这两个人应该是有不同的权限(在授权表中)。可是如果授权表以用户NO作为关联用户的键,则后来的人就有了原来那个人的权限了,是不对的。(这里也说明了下面会提到的,在不是万万确定的情况下,不要设置外键,或是说删除等的限制,对于外键的应用,是一个可以研究的问题。) 
复合主键在作自动处理,如页面批量删除等或是编码公用性上就不方便了。当然性能也没有单主键好。如果使用Hibernate等O/R工具的话,参考其映射例子,对于简单的单为了表达多对多的关系,则只有两个相对就表的ID,则此表无其它意思,可以不需要上面提到的无意义的ID作为主键了。但这种较少,看定单/定单条目/产品的例子中,将产品条目作为定单的SET/LIST实现的话,好像定单条目中则不能加id标识了。(不过,作为业务表设计应该是主,而 HIBERNATE应该是末了,不推荐使用HIBERNATE来推动业务数据库设计了。因此,HIBERNATE中的id标识主要是作为 HIBERNATE的标识,与我上面的讲到的表设计中的ID是可能分开的,只不过对于上面提到的情况,刚好可以很好的配合吧。此种情况下,我们如果用 HIBERNATE,也可以将ID字段作为LIST/SET的composite-element一个属性property来处理,只是不能利用 Hibernate class id标识的generator而已,即在插入时要自己赋值,也可以编程中取得如sequence或UUID等,保持数据库层的一致性) 


对主键的选择:一是业务编号作主键,会有如上提到的问题。二是无意义自动编号,如序列SEQUENCE。三是MAX加一,有时用业务编号也是如此。如果直接用本表来实现,max(NO)+1,当数据增加后,会严重影响性能。四是自制加一,是对MAX加一的一种改进实现,用一表编号控制档NO_CTRL来实现的。在后面会谈到,这也是一个比较可研究的命题。主要用来生成业务编码,且带有业务编码规则。五是UUID/GUID,因为UUID/GUID也是无意义,且无序的,并且更长,相对于SEQUENCE来说,是没有优势的。(不过也有特殊情况,是否需要所有的表或部分表的主键唯一,如在一次设计的统一的审核流程,审核规则的时候,则以定单,采购单等的一主键ID作为审核表的外键) 如在Hibernate中则可以定制就很好。 


2,命名 
表,字段,视图等都以'_'下划线分隔各个意义单词,这样方便从数据持久向对象的转换,参照JAVA命名规则,这样例如用JB或自己写程序转换则以'_'分隔组成JAVA属性首字母大小写等。 
表名/视图名按你的业务模块或是'T'/'V'等为前缀加以区分。 

从持久到对象类型,table到java 类的转换最好全部规则一致,(除非生成JAVA后可能还需要增加编程中用的属性),最好的方法是用工具生成,如用JB生成CMP一样。当然如用HIBERNATE则也可能是反向的,由JAVA类设计(UML),利用xDoclet等生成hbm.xml,再根据hbm.xml导出数据库DDL。 
这种完全规则一样,可能对于在利用如反射等作编码完成所有的表的某一功能时很有用。 

3,一般在设计时所有表都应该有如下字段(除小数配置性表,如下面提到的no_ctrl,在你不确定时也可以加上,先不用即可), 
ID:NUMBER 主键 
NO/复合字段:业务主键(唯一KEY) 
CREATE_USR:创建人(NOT NULL) 
CREATE_TIME:创建时间(NOT NULL) 
MODIFY_USR:最后修改人 
MODIFY_TIME:最后修改时间 
DELETE_FLAG:0/1,T/F,TRUE/FALSE 删除标志(default 'F' NOT NULL) 
DELETE_USR:删除人 
DELETE_TIME:删除时间 
VERSION:NUMBER default 1 版本(NOT NULL) 
在设计时可能还没有要求到删除时只作标记,而是直接删除,可能是客户还没想到或提出。但作设计人员是应该想到可能的扩展的。用户乐观锁定的VERSION也一样,可能在开始时还没有用到,但也应是一种选择锁定的方法的。 
可以选择以上的时间/版本等是采用编程来实现(应用层来赋值)或是采用数据库层的TIRGGER来实现。 

在设计字段时,最好想想是否有DEFAULT值。加上注释,特别是如DELETE_FLAG这样的字段。 

4,字段类型(还没写,主要指如时间类型,有的是用char(8)20010101 或char(10)2001-01-01等。不有标志,如状态,类型等,是用char还是number呢?(请大家发表这对于数据库层及编程方便性上给个意见。) 

5,对于业务编码或是自制编号加一的设计及思考。 

如上提到可以用自制编号加一来设计主键。但同时也讲到最好是别设一无意义的纯主键。所以一般是用自制来设计“业务编号”,因为业务编号可能有前后缀,且一般要求顺序,连续等。所以如下主要探讨一下业务编码的问题: 
a,编号连续问题,使用MAX + 1,如果在执行INSERT语句时,直接使用则一定保证多用户连续。即INSERT INTO IN_BOX VALUES(MAX(IN_NO)+1,...)。如果有前后缀,如果规则一定,也可以使用如ORACLE的SUBSTR等来实现。如果在编程中是先用一个SELECT MAX(IN_NO)...,再执行INSERT,则会出现多用户时后者重复的情况。使用此更大的问题是上面讲到的性能问题,所以不推荐。 
b,使用自制一个NO_CTRL表来保存(注:如ORACLE的序列在内部据说也是如此来实现的),基本的NO_CTRL是这样的,NO_CTRL (ID,TABLE_CODE,COLUMN_CODE,CURR_NUM)。基本作法是在完成插入时加1。(可能对于你的设计中某个表存的数据是单据,单据基本数据项一样,可是不同种类的单据编号不同,各自连续时,则此表要作相应修改,如果要满足类似所有情况可能得更多思考了)。一般会有一个专门的类来完成取得NO的功能。 
c,对于某些业务,特别是ERP中的单据等,是在用户录入界面时就得显示单据号,这个问题比较复杂。最简单的是页面生成下一个NO,用 noCtrl.nextNo(table,column),页面和其它录入项一样传入后台进行数据新增。noCtrl的nextNo的实现是select curr_no+1,update curr_no+1,即取回并加一更新。此方法有很严重的问题是就算是单用户也不能保证业务编号连续,因为在取回时就更新了,如果业务执行新增出现异常,则此编号则浪费了。所以应该对此方法加以改进,主要是采用“延迟更新”来实现。即nextNo()方法是只执行查询,返回下一个编号,显示在页面上,在后台执行业务新增时,在一个事务中,执行更新加一的动作。注意考虑多用户的情况,业务新增中要重新执行取得号码(因为在你执行取回号到执行新增中间可能已被另一用户更新了,你的编号已被使用了)。所以你的业务新增会是这样的,tx.begin(),ctrlNo.nextNo(table,column), insert(业务),ctrlN.update(),tx.end().对于ctrlNo.update()为了更进一步保证一致,可以带上where currNo=currNo。如是乐观锁加version一样。同时可能存在取加一个号要连续用多个的情况,最好则采用加锁读取来实现。 
d,上面主要是讲到业务编号的连续,是数字。但一般业务编号并不只是数字,而是一个有前后缀,且按一定规则生成的。所以采用NO_CTRL也是很好的。这样可能会是这样的,NO_CTRL(ID,TABLE_CODE,COLUMN_CODE,PREFIX,POSTFIX,CURR_NUM, NUM_LEN)。即加上前,后缀规则和数字号长度。 

Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=428282 

你可能感兴趣的:(编程,数据库,Hibernate,table,delete,insert)