论坛首页 Java企业应用论坛

[讨论]业务主键 Vs. 逻辑主键,到底哪个好?

浏览 86064 次
该帖已经被评为精华帖
作者 正文
   发表时间:2004-03-11  
jaqwolf 写道
o6z,你说的不要使用主建,那么一致性可能会损失。。。。人为的控制可能会失误呢?。。。


我想,使用这种模式的时候,其实所有的差错控制都由程序来做,比如,在内存中就会使用集合而不是表或包来存储数据,而数据库就只是单纯的存储数据,这样所有的高级数据库特性都不需要了,也不必去买高级数据库了,mysql之类的就可以了

这样的系统,我想硬件的可靠性必然非常高
0 请登录后投票
   发表时间:2004-03-11  
哈哈,我第一次听到使用IBM小型机、DELL专业服务器的硬件系统,呵呵,数据库用mysql。那我想大家都会笑死(就像一个人花2万元买台电脑,不是别的,练五笔,哈哈)
0 请登录后投票
   发表时间:2004-03-11  
gigix 写道
我想这个讨论就可以告一段落了。Martin Fowler在POEAA里面也说,O/R mapping的主键绝对不应该有业务含义。如果谁还感觉业务字段可以用来做主键,不妨试着想想这个字段是不是真的真的永远不会变?


引用伟人的话:一国两制,50年不变。50年以后呢?who knows...... 这个世界上确实是没有真的真的永远不会变的东西。但是从系统设计的观点上来看,我们为什么不可以认为一些业务字段是不变的?如果原来的设计不是太差的话,即便像身份证号码升号这样的问题也是容易解决的:主流的数据库都提供了级联操作,如果你当初设计的时候有按照数据库范式的话,那么一句update语句就可以cascade update所有的相关资料。或者通过IDCard这个对象来获得对应的对象,写一个upgrade程序也不是太困难。


gigix 写道
至于性能问题,我看不出业务主键比逻辑主键强在哪里。一个是“SELECT * WHERE id = ?”,一个是“SELECT * WHERE identityCard = ?”,这有什么不同呢?


No,No,是有性能差别的,你测试一下就知道了,比如我要查询gigix有哪些车:
select * from bike, idcard where bike.idCardId = idcard.id and idcard.no = gigix.idCardNo,你得join 2张table。
用业务主键就是select * from bike where idCardNo = gigix.idCardno 一个table搞定了。类似这样的查询如果是join多张master data table的话,使用逻辑主键在性能上的下降就更明显了。而且第2个查询在编码上也简单,易读。
0 请登录后投票
   发表时间:2004-03-11  
Quake Wang 写道
gigix 写道
我想这个讨论就可以告一段落了。Martin Fowler在POEAA里面也说,O/R mapping的主键绝对不应该有业务含义。如果谁还感觉业务字段可以用来做主键,不妨试着想想这个字段是不是真的真的永远不会变?


引用伟人的话:一国两制,50年不变。50年以后呢?who knows...... 这个世界上确实是没有真的真的永远不会变的东西。但是从系统设计的观点上来看,我们为什么不可以认为一些业务字段是不变的?如果原来的设计不是太差的话,即便像身份证号码升号这样的问题也是容易解决的:主流的数据库都提供了级联操作,如果你当初设计的时候有按照数据库范式的话,那么一句update语句就可以cascade update所有的相关资料。或者通过IDCard这个对象来获得对应的对象,写一个upgrade程序也不是太困难。


gigix 写道
至于性能问题,我看不出业务主键比逻辑主键强在哪里。一个是“SELECT * WHERE id = ?”,一个是“SELECT * WHERE identityCard = ?”,这有什么不同呢?


No,No,是有性能差别的,你测试一下就知道了,比如我要查询gigix有哪些车:
select * from bike, idcard where bike.idCardId = idcard.id and idcard.no = gigix.idCardNo,你得join 2张table。
用业务主键就是select * from bike where idCardNo = gigix.idCardno 一个table搞定了。类似这样的查询如果是join多张master data table的话,使用逻辑主键在性能上的下降就更明显了。而且第2个查询在编码上也简单,易读。


实际上即使使用业务主键,我也只需要select * from bike where idCardNo = gigix.idCardno就足够了。对于idCardNo的唯一性,在我新增记录的时候,我会通过查询相同的idCardNo是否存在来得到保证的。难道你把判断重复性的任务全部交给数据库吗?然后通过异常编号来识别应该向上一层报出什么信息的异常吗?
0 请登录后投票
   发表时间:2004-03-11  
呵呵,楼上所想到总之就是怎么弥补,呵呵,你认为的业务主键不会变,如果变了,呵呵,就通过各种各样的方法弥补。哈哈,连客户都无法真正确定,你又怎么能确定呢?
   就说级联吧,如果你做供应链系统(不知楼上是否做过),那么从最开始的单到最后都会引用,哈哈,如果你开发完后,客户告诉你不行,业务主键要改,我看你怎么办?你不会告诉我从头到尾一直级联更新下去吧???
   楼上比较性能,只比较join表的时候性能,哈哈,我告诉你,你处理复杂业务时的SQL语句远远要超过简单地join几张表。再说,级联更新的性能更差了,不信你自己试试就知道了。
   总之感觉一句话,楼上只是就只图方便,项目经验太少啊~~~~~~~,好的东西该学习,而不是排斥。
0 请登录后投票
   发表时间:2004-03-12  
凤舞凰扬 写道
呵呵,楼上所想到总之就是怎么弥补,呵呵,你认为的业务主键不会变,如果变了,呵呵,就通过各种各样的方法弥补。哈哈,连客户都无法真正确定,你又怎么能确定呢?

很多master table的业务主键是不会变的,从以往的项目中我们可以发现很多的例子:
1. 测量单位, 1KG = 1000G
2. 国家代码, China = CHN
3. 币种, 人民币=RMB
4. Character Set, 如GB2312, UTF-8
5. File Extension 和 MimeType .pdf = application/pdf
6. ......
7. 还有一大类是用于表示Type和Status类型的master table, 比如Payment Method Type, Payment Status等等.

如果硬要挑刺说你怎么可以排除以后人民币代码要改成USD,美元要改成RMB的可能? 那么我也没有办法. 计划赶不上变化,真的出现这样的情况了,就再用我前面提到的方法去做所有数据的一次性更新.

凤舞凰扬 写道
就说级联吧,如果你做供应链系统(不知楼上是否做过),那么从最开始的单到最后都会引用,哈哈,如果你开发完后,客户告诉你不行,业务主键要改,我看你怎么办?你不会告诉我从头到尾一直级联更新下去吧???

不明白你的意思,可以讨论得具体一些么? 我说的级联更新是用在业务主键需要按照最新的规范来进行upgrade的时候用的,并不是用在系统的日常操作上的.

凤舞凰扬 写道
楼上比较性能,只比较join表的时候性能,哈哈,我告诉你,你处理复杂业务时的SQL语句远远要超过简单地join几张表。

既然join几张table这样简单业务的sql语句性能上就有差异, 那么在同等的复杂查询下面, 当然还是使用业务主键的性能比较好.而且很多情况下面复杂业务的sql语句需要关联很多master table, 性能差异就更明显了.

凤舞凰扬 写道
总之感觉一句话,楼上只是就只图方便,项目经验太少啊~~~~~~~,好的东西该学习,而不是排斥。

送一个单词给你: KISS (Keep It Simple and Stupid).
0 请登录后投票
   发表时间:2004-03-12  
想不到这个问题还要讨论这么久。
首先,逻辑主键正式的名字叫做代理主键。
1 我不知道楼上的级联更新是怎么做的,简单的说一个公安系统的问题,上千万的常住人口数据,几百万的自行车数据、机动车、相关的违章信息以及上亿的刑事、通缉、在逃案件和其他相关数据,杂七杂八的可以多达几十层的关系,你什么时候做更新?一个企业里面某个商品的代码相关的数据累计可以扩展到几十万,上百万条记录,商品交易的总数据量可以达到上亿,你什么时候更新?如果这些数据备份到外面了,你是不是把数据导进来,更新了以后再导出去?
Keep it Simple != Keep It Stupid,数据库的范式和OAOO不是嘴巴上说说的,软件维护成本为什么远远高于开发成本也不是没有原因的

2在业务处理过程中,我们已经在Hibernate里面看到,通过各种策略(例如Lazy,Cache),可以在绝大多数情况下只从数据库加载一次或有限次数。而代理主键在这里是一个至关重要的概念, 特别是使用业务主键最后往往需要复合主键。数据可以在导航过程中逐步加载,也可以一次连接放在缓存中,我不觉得会有什么效率问题。
当然在统计过程中,由于需要连接多张表格,可能造成效能的降低,但是统计报表适当的效率降低是可以接受的。

3即使不是维护数据,在日常操作中,对象更新时候的高效率,也能说明问题。更新任何一个内存中的业务对象信息只需要做一个where id = ?即可。例如,订单号和订单行主从结构,你用订单号作为主键,然后修改订单行的信息,你需要做一个where orderNo=? And oderLineSeqno=?,而如果订单行是有代理主键的,那么只需要做一个where oderLinerOID=?即可。至于可能多级主从关系,在复杂的业务系统中也屡见不鲜。还有,如果发现订单号码输错了,那么你是不是不让修改订单号码,还是做一个级联更新,如果这个订单已经和其他数据关联,那么你怎么办?你不要说这种情况是很少的,用户输错你的业务主键是经常有的事情,这时候那个效率更高?

4你举的那些例子:
引用

1. 测量单位, 1KG = 1000G
2. 国家代码, China = CHN
3. 币种, 人民币=RMB
4. Character Set, 如GB2312, UTF-8
5. File Extension 和 MimeType .pdf = application/pdf
6. ......
7. 还有一大类是用于表示Type和Status类型的master table, 比如Payment Method Type, Payment Status等等.

所谓的参考数据,除非是永远不变,写死在程序里面的,那这个时候就根本不需要单独的表。如果你不写死在程序里面,也就是说用户可以修改的,那照样需要代理主键。最简单的是,系统维护员不小心把RMB输成了RXB,你是不是在历史数据中永远是RXB?Type、State这种可以增加的代码更容易出现这个问题!
至于币种特别是计量单位是一个相当复杂的模型,更需要代理主键。
0 请登录后投票
   发表时间:2004-03-12  
我并不是说业务主键不会变,业务主键可以随时变;
我只是保证在一个表中业务主键永远不会重复就行了;
而发生关联的只有逻辑主键。
有什么不妥吗???
0 请登录后投票
   发表时间:2004-03-12  
实际上我说的那种依赖内存的情况是在高负载应用中经常使用的方法,包括数据库的内存表的方法也是应用这样的手段。当我听到大家谈效能的时候就会想到到底怎么样才可以满足那些高效能约束的应用,而才介绍了这样的一个方法。当然这样做是有代价的,也就是开发成本一下子就高上去了。但是这可以说是在低硬件要求的情况下的最好手段了。不过我见不到大家有太多的必要都来这样实现你的程序,使用主键自然是一种选择。不过我想大家应该明白每一种手段的约束。而实际上又有多少人在没有接触过这些OR之前,真正的去思考过如何利用关系数据库存储对象呢?实际上我在2000年以前XML还没有人知道的时候,就考虑过在数据库中如何存储数据,只不过是效率还不高。但是绝对是可以实现的,方法也并不复杂。也就是把对象的越是存储在一个表中,对象的属性按照属性字段的数据类型存储在不同的表中。然后把对象表放在内存中,以提高效率。
0 请登录后投票
   发表时间:2004-03-12  
potian 写道
1 我不知道楼上的级联更新是怎么做的,简单的说一个公安系统的问题,上千万的常住人口数据,几百万的自行车数据、机动车、相关的违章信息以及上亿的刑事、通缉、在逃案件和其他相关数据,杂七杂八的可以多达几十层的关系,你什么时候做更新?一个企业里面某个商品的代码相关的数据累计可以扩展到几十万,上百万条记录,商品交易的总数据量可以达到上亿,你什么时候更新?如果这些数据备份到外面了,你是不是把数据导进来,更新了以后再导出去?


在你举的公安系统这个例子当中你是没有办法找到一个业务主键的,用身份证号码?未成年人没有身份证怎么办?所以请不要拿虚构的项目来讨论,很容易离题的。

再拿你举的企业商品来说,一个ProductID通常都是按照一定规则生成的, 熟悉自己企业或者供应商产品的企业用户,只需要看ProductID就可以知道它是一个什么产品,所以各种需要和外部系统交互的Order(如Sales Order, Delivery Order, Purchase Order)上只会显示ProductID,理由很简单,用户不关心代理主键。如果ProductID由于企业Product命名规则的改变需要进行升级的话,也绝对不是代理主键方案那么简单的修改对应的ProductID就OK了,很多已经发单的历史数据你怎么处理,比如已经打印出来保留在客户那边的订单怎么办?这种情况下面我们是否考虑使用History Table来进行系统的升级?否则客户就找不到对应的Product了。

另外我很好奇你的那个生份证升级的问题后来是怎么解决的?升级系统修改到代理主键,还是做一次性的update?

potian 写道
2在业务处理过程中,我们已经在Hibernate里面看到,通过各种策略(例如Lazy,Cache),可以在绝大多数情况下只从数据库加载一次或有限次数。而代理主键在这里是一个至关重要的概念, 特别是使用业务主键最后往往需要复合主键。数据可以在导航过程中逐步加载,也可以一次连接放在缓存中,我不觉得会有什么效率问题。


关于Hibernate的问题,我没有使用Hibernate的实际经验,但是从文档上来看复合主键确实会有不小的麻烦。如果使用Hibernate的话,值得考虑让所有的Domain Object都extends一个有ID属性的Entity。

potian 写道
3即使不是维护数据,在日常操作中,对象更新时候的高效率,也能说明问题。更新任何一个内存中的业务对象信息只需要做一个where id = ?即可。例如,订单号和订单行主从结构,你用订单号作为主键,然后修改订单行的信息,你需要做一个where orderNo=? And oderLineSeqno=?,而如果订单行是有代理主键的,那么只需要做一个where oderLinerOID=?即可。至于可能多级主从关系,在复杂的业务系统中也屡见不鲜。还有,如果发现订单号码输错了,那么你是不是不让修改订单号码,还是做一个级联更新,如果这个订单已经和其他数据关联,那么你怎么办?你不要说这种情况是很少的,用户输错你的业务主键是经常有的事情,这时候那个效率更高?


用户在使用你的系统的时候,他是不可能知道oderLinerOID (如32位的UUID,谁会去记?),举一个常见的user case:订单管理人员接到客户一个电话,说订单号为0011上的第2个OrderItem好像单价有问题,需要查一下。那么你的系统肯定是需要提供一个用户界面供订单管理人员输入orderNo和orderItemSeqNo,然后找到这个单笔记录,这个时候你的系统还是得用where orderNo = 0011 and orderItemSeqNo = 2下去查找.

关于订单号码输入错误的问题,至少我没有见过哪个系统是允许用户在creaet order的时候可以自由输入OrderNo的,那些号码都是按照一定的规则自动生成的,用户是没有机会出错的。

potian 写道
所谓的参考数据,除非是永远不变,写死在程序里面的,那这个时候就根本不需要单独的表。如果你不写死在程序里面,也就是说用户可以修改的,那照样需要代理主键。最简单的是,系统维护员不小心把RMB输成了RXB,你是不是在历史数据中永远是RXB?Type、State这种可以增加的代码更容易出现这个问题!
至于币种特别是计量单位是一个相当复杂的模型,更需要代理主键。


写死在程序里面是一个不好的做法,系统需要多增加一种Type或者Status时候你就得必须修改旧有代码了。像type和status这种和系统密切相关的数据,我们也不可能放手给用户修改,除非在最初的项目协议书里面就有这样的功能要求,才会去考虑这种复杂的做法。那么用户在系统运行以后需要如果要加Type或者Status,他是需要付维护费用的。

币种都是我们预先建立好的,最终用户是没有方法修改的,如果你说多出一个国家来怎么办?还是我前面的话,计划赶不上变化。一个例子:当欧元出来的时候, SAP还不是要release patch,让它的ERP系统支持欧元这个单位......,SAP都这样做,我们未何不KISS呢?

计量单位模型我倒没有觉得多么复杂,UoM (Unit of Measurement)和UomConversion2个对象就搞定了。UoM的主键用业务主键,比如公斤这个对象:{UoMID:WEIGHT_KG, description: Kilogram, abbreviation: kg, typeId: WEIGHT_MEASURE},那么它转化到克的UomConversion对象就是:{uomIdFrom: WEIGHT_KG, uomIdTo: WEIGHT_G, formula: $WEIGHT_G = $WEIGHT_KG * 1000}。如果你用代理主键的话,UomConversion: {uomIdFrom: 1002, uomIdTo: 1003, forumla: $1003 = $1002 * 1000},这样系统维护人员看到UomConversion的数据的话,就很难理解了。
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics