背景:数年的工做中,已经设计了不少系统或产品的数据库,有单机的、有局域网环境下的、也有互联网环境下的,对于不一样的环境,设计考虑都有所不一样。即便对于相同的环境,也会由于业务或者数据量的不一样而有不一样的设计。近期,又要设计一款互联网产品的数据库(MySQL服务)。通过以前的积累,在表的ID设计这个环节就进行了大量的分析、比较、学习,对ID的设计也有了更系统和深入的认知,把本身学习实践到的知识总结下来,分享给你们。html
对于关系数据库来讲,设计每一个表的第一步都会肯定其主键,主键就是ID。在“常识”之中,int型的自增id,字符串类型的uuid,其余与业务相关的惟一键…都是咱们做为主键的选择。那么是否是说在一张表中只要能保证值惟一的属性列均可以作为主键或者更合适作主键呢?sql
那咱们首先清晰几个概念:数据库
逻辑主键(代理主键):在数据库表中采用一个与当前表中业务逻辑信息无关的字段做为其主键,或称为“伪主键”;安全
业务主键(天然主键):在数据库表中把具备业务逻辑含义的字段做为主键;架构
举一个很常见的例子:一张用户信息表,列属性有id、用户名、手机号…,其中用户名和手机号(做为登陆帐号两者都是惟一的)。其中id即可做为逻辑主键,用户名和手机号均可以做为业务主键。那我是否是能够随便选一个,甚至我选择了业务主键均可以不要逻辑主键?ide
那么咱们首先来看看逻辑主键和业务主键之间纷纷烈烈的观点分歧:post
支持逻辑主键性能
表经过主键来保证每条记录的惟一性,表的主键应当不具备任何业务含义,由于任何有业务含义的列都有改变的可能性。关系数据库学的最重要的一个理论就是:不要给关键字赋予任何业务意义。假如关键字具备了业务意义,当用户决定改变业务含义,也许他们想要为关键字增长几位数字或把数字改成字母,那么就必须修改相关的关键字。一个表中的主关键字有可能被其余表做为外键。就算是一个简单的改变,譬如在客户号码上增长一位数字,也可能会形成极大的维护上的开销。学习
为了使表的主键不具备任何业务含义,一种解决方法是使用代理主键,例如为表定义一个不具备任何业务含义的ID字段(也能够叫其余的名字),专门做为表的主键。
——孙卫琴《精通Hibernate:Java对象持久化技术详解》P8ui
使用逻辑主键的主要缘由是,业务主键一旦改变则系统中关联该主键的部分的修改将会是不可避免的,而且引用越多改动越大。而使用逻辑主键则只须要修改相应的业务主键相关的业务逻辑便可,减小了由于业务主键相关改变对系统的影响范围。业务逻辑的改变是不可避免的,由于“永远不变的是变化”,没有任何一个公司是一成不变的,没有任何一个业务是永远不变的。最典型的例子就是***升位和驾驶执照号换用***号的业务变动。并且现实中也确实出现了***号码重复的状况,这样若是用***号码做为主键也带来了难以处理的状况。固然应对改变,能够有不少解决方案,方案之一是作一新系统与时俱进,这对软件公司来讲确实是件好事。使用逻辑主键的另一个缘由是,业务主键过大,不利于传输、处理和存储。我认为通常若是业务主键超过8字节就应该考虑使用逻辑主键了,由于int是4字节的,bigint是8字节的,而业务主键通常是字符串,一样是 8 字节的 bigint 和 8 字节的字符串在传输和处理上天然是 bigint 效率更高一些。想象一下 id 为”12345678” 和 id为12345678 的汇编码的不一样就知道了。固然逻辑主键不必定是 int 或者 bigint ,而业务主键也不必定是字符串也能够是 int 或 datetime 等类型,同时传输的也不必定就是主键,这个就要具体分析了,可是原理相似,这里只是讨论一般状况。同时若是其余表须要引用该主键的话,也须要存储该主键,那么这个存储空间的开销也是不同的。并且这些表的这个引用字段一般就是外键,或者一般也会建索引方便查找,这样也会形成存储空间的开销的不一样,这也是须要具体分析的。
使用逻辑主键的再一个缘由是,使用 int 或者 bigint 做为外键进行联接查询,性能会比以字符串做为外键进行联接查询快。原理和上面的相似,这里再也不重复。
使用逻辑主键的再一个缘由是,存在用户或维护人员误录入数据到业务主键中的问题。例如错把 RMB 录入为 RXB ,相关的引用都是引用了错误的数据,一旦须要修改则很是麻烦。若是使用逻辑主键则问题很好解决,若是使用业务主键则会影响到其余表的外键数据,固然也能够经过级联更新方式解决,可是不是全部都能级联得了的。
——SwitchBlade的总结
支持业务主键
若是你的表中包含一列能确保惟1、非空以及可以用来定位一条记录,就别仅仅由于传统而以为有必要再加上一个伪主键。
——Bill Karwin 《SQL反模式》 p41
使用业务主键的主要缘由是,增长逻辑主键就是增长了一个业务无关的字段,而用户一般都是对于业务相关的字段进行查找(好比员工的工号,书本的 ISBN No. ),这样咱们除了为逻辑主键加索引,还必须为这些业务字段加索引,这样数据库的性能就会降低,并且也增长了存储空间的开销。因此对于业务上确实不常改变的基础数据而言,使用业务主键不失是一个比较好的选择。另外一方面,对于基础数据而言,通常的增、删、改都比较少,因此这部分的开销也不会太多,而若是这时候对于业务逻辑的改变有担心的话,也是能够考虑使用逻辑主键的,这就须要具体问题具体分析了。使用业务主键的另一个缘由是,对于用户操做而言,都是经过业务字段进行的,因此在这些状况下,若是使用逻辑主键的话,必需要多作一次映射转换的动做。我认为这种担忧是多余的,直接使用业务主键查询就能获得结果,根本不用管逻辑主键,除非业务主键自己就不惟一。另外,若是在设计的时候就考虑使用逻辑主键的话,编码的时候也是会以主键为主进行处理的,在系统内部传输、处理和存储都是相同的主键,不存在转换问题。除非现有系统是使用业务主键,要把现有系统改为使用逻辑主键,这种状况才会存在转换问题。暂时没有想到还有什么场景是存在这样的转换的。
使用业务主键的再一个缘由是,对于银行系统而言安全性比性能更加剧要,这时候就会考虑使用业务主键,既能够做为主键也能够做为冗余数据,避免由于使用逻辑主键带来的关联丢失问题。若是因为某种缘由致使主表和子表关联关系丢失的话,银行但是会面临没法挽回的损失的。为了杜绝这种状况的发生,业务主键须要在重要的表中有冗余存在,这种状况最好的处理方式就是直接使用业务主键了。例如***号、存折号、卡号等。因此一般银行系统都要求使用业务主键,这个需求并非出于性能的考虑而是出于安全性的考虑。
——SwitchBlade的总结
因此说明逻辑主键和业务主键的选择并非拍脑瓜的结果,而是根据不一样的应用场景、不一样的需求决策的结果。
若是咱们使用整数类型的自增id做为主键又会面临什么问题呢?
对于数据量很是大的表后期每每会涉及到水平分表的需求,这时这个自增主键会成为阻碍。(其实关于这种状况也会有解决方案,请参见文章《又拍网架构中的分库设计》
咱们再换一个角度考虑主键的选择:数据类型。
整数类型:
整数类型每每是id列最好的选择,由于效率最高而且可使用数据库的自增主键。
字符串类型
字符串类型相比整数类型确定更消耗空间,也会比整数类型操做慢。我主要使用的是Mysql,关于这个话题的解释建议看《高性能MySQL》第三版 P125。
我采用的方案(MySQL):使用自增id做为主键,以此来应对插入效率问题;采用uuid作逻辑id,拥有了逻辑主键的诸多好处,并且能够用来应对以后的水平分表。