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