背景:数年的工作中,已经设计了很多系统或产品的数据库,有单机的、有局域网环境下的、也有互联网环境下的,对于不同的环境,设计考虑都有所不同。即使对于相同的环境,也会因为业务或者数据量的不同而有不同的设计。近期,又要设计一款互联网产品的数据库(MySQL服务)。经过之前的积累,在表的ID设计这个环节就进行了大量的分析、比较、学习,对ID的设计也有了更系统和深刻的认知,把自己学习实践到的知识总结下来,分享给大家。


主键id的选择

对于关系数据库来说,设计每个表的第一步都会确定其主键,主键就是ID。在“常识”之中,int型的自增id,字符串类型的uuid,其他与业务相关的唯一键…都是我们作为主键的选择。那么是不是说在一张表中只要能保证值唯一的属性列都可以做为主键或者更合适做主键呢?

那我们首先清晰几个概念:

  • 逻辑主键(代理主键):在数据库表中采用一个与当前表中业务逻辑信息无关的字段作为其主键,或称为“伪主键”;

  • 业务主键(自然主键):在数据库表中把具有业务逻辑含义的字段作为主键;

举一个很常见的例子:一张用户信息表,列属性有id、用户名、手机号…,其中用户名和手机号(作为登录账号二者都是唯一的)。其中id便可作为逻辑主键,用户名和手机号都可以作为业务主键。那我是不是可以随便选一个,甚至我选择了业务主键都可以不要逻辑主键?

那么我们首先来看看逻辑主键和业务主键之间纷纷烈烈的观点分歧:

  • 支持逻辑主键

    表通过主键来保证每条记录的唯一性,表的主键应当不具有任何业务含义,因为任何有业务含义的列都有改变的可能性。关系数据库学的最重要的一个理论就是:不要给关键字赋予任何业务意义。假如关键字具有了业务意义,当用户决定改变业务含义,也许他们想要为关键字增加几位数字或把数字改为字母,那么就必须修改相关的关键字。一个表中的主关键字有可能被其他表作为外键。就算是一个简单的改变,譬如在客户号码上增加一位数字,也可能会造成极大的维护上的开销。

    为了使表的主键不具有任何业务含义,一种解决方法是使用代理主键,例如为表定义一个不具有任何业务含义的ID字段(也可以叫其他的名字),专门作为表的主键。
    ——孙卫琴《精通Hibernate:Java对象持久化技术详解》P8

  • 使用逻辑主键的主要原因是,业务主键一旦改变则系统中关联该主键的部分的修改将会是不可避免的,并且引用越多改动越大。而使用逻辑主键则只需要修改相应的业务主键相关的业务逻辑即可,减少了因为业务主键相关改变对系统的影响范围。业务逻辑的改变是不可避免的,因为“永远不变的是变化”,没有任何一个公司是一成不变的,没有任何一个业务是永远不变的。最典型的例子就是***升位和驾驶执照号换用***号的业务变更。而且现实中也确实出现了***号码重复的情况,这样如果用***号码作为主键也带来了难以处理的情况。当然应对改变,可以有很多解决方案,方案之一是做一新系统与时俱进,这对软件公司来说确实是件好事。

    使用逻辑主键的另外一个原因是,业务主键过大,不利于传输、处理和存储。我认为一般如果业务主键超过8字节就应该考虑使用逻辑主键了,因为int是4字节的,bigint是8字节的,而业务主键一般是字符串,同样是 8 字节的 bigint 和 8 字节的字符串在传输和处理上自然是 bigint 效率更高一些。想象一下 id 为”12345678” 和 id为12345678 的汇编码的不同就知道了。当然逻辑主键不一定是 int 或者 bigint ,而业务主键也不一定是字符串也可以是 int 或 datetime 等类型,同时传输的也不一定就是主键,这个就要具体分析了,但是原理类似,这里只是讨论通常情况。同时如果其他表需要引用该主键的话,也需要存储该主键,那么这个存储空间的开销也是不一样的。而且这些表的这个引用字段通常就是外键,或者通常也会建索引方便查找,这样也会造成存储空间的开销的不同,这也是需要具体分析的。

    使用逻辑主键的再一个原因是,使用 int 或者 bigint 作为外键进行联接查询,性能会比以字符串作为外键进行联接查询快。原理和上面的类似,这里不再重复。

    使用逻辑主键的再一个原因是,存在用户或维护人员误录入数据到业务主键中的问题。例如错把 RMB 录入为 RXB ,相关的引用都是引用了错误的数据,一旦需要修改则非常麻烦。如果使用逻辑主键则问题很好解决,如果使用业务主键则会影响到其他表的外键数据,当然也可以通过级联更新方式解决,但是不是所有都能级联得了的。
    ——SwitchBlade的总结

  • 支持业务主键

    如果你的表中包含一列能确保唯一、非空以及能够用来定位一条记录,就别仅仅因为传统而觉得有必要再加上一个伪主键。
    ——Bill Karwin 《SQL反模式》 p41

  • 使用业务主键的主要原因是,增加逻辑主键就是增加了一个业务无关的字段,而用户通常都是对于业务相关的字段进行查找(比如员工的工号,书本的 ISBN No. ),这样我们除了为逻辑主键加索引,还必须为这些业务字段加索引,这样数据库的性能就会下降,而且也增加了存储空间的开销。所以对于业务上确实不常改变的基础数据而言,使用业务主键不失是一个比较好的选择。另一方面,对于基础数据而言,一般的增、删、改都比较少,所以这部分的开销也不会太多,而如果这时候对于业务逻辑的改变有担忧的话,也是可以考虑使用逻辑主键的,这就需要具体问题具体分析了。

    使用业务主键的另外一个原因是,对于用户操作而言,都是通过业务字段进行的,所以在这些情况下,如果使用逻辑主键的话,必须要多做一次映射转换的动作。我认为这种担心是多余的,直接使用业务主键查询就能得到结果,根本不用管逻辑主键,除非业务主键本身就不唯一。另外,如果在设计的时候就考虑使用逻辑主键的话,编码的时候也是会以主键为主进行处理的,在系统内部传输、处理和存储都是相同的主键,不存在转换问题。除非现有系统是使用业务主键,要把现有系统改成使用逻辑主键,这种情况才会存在转换问题。暂时没有想到还有什么场景是存在这样的转换的。

    使用业务主键的再一个原因是,对于银行系统而言安全性比性能更加重要,这时候就会考虑使用业务主键,既可以作为主键也可以作为冗余数据,避免因为使用逻辑主键带来的关联丢失问题。如果由于某种原因导致主表和子表关联关系丢失的话,银行可是会面临无法挽回的损失的。为了杜绝这种情况的发生,业务主键需要在重要的表中有冗余存在,这种情况最好的处理方式就是直接使用业务主键了。例如***号、存折号、卡号等。所以通常银行系统都要求使用业务主键,这个需求并不是出于性能的考虑而是出于安全性的考虑。
    ——SwitchBlade的总结

所以说明逻辑主键和业务主键的选择并不是拍脑瓜的结果,而是根据不同的应用场景、不同的需求决策的结果。

如果我们使用整数类型的自增id作为主键又会面临什么问题呢?
对于数据量非常大的表后期往往会涉及到水平分表的需求,这时这个自增主键会成为阻碍。(其实关于这种情况也会有解决方案,请参见文章《又拍网架构中的分库设计》

ID数据类型的选择

我们再换一个角度考虑主键的选择:数据类型。

  • 整数类型:
    整数类型往往是id列最好的选择,因为效率最高并且可以使用数据库的自增主键。

  • 字符串类型
    字符串类型相比整数类型肯定更消耗空间,也会比整数类型操作慢。我主要使用的是Mysql,关于这个话题的解释建议看《高性能MySQL》第三版 P125。

我采用的方案(MySQL):使用自增id作为主键,以此来应对插入效率问题;采用uuid做逻辑id,拥有了逻辑主键的诸多好处,而且可以用来应对之后的水平分表。

数据库的唯一标示符(ID)的选择相关推荐

  1. iOS唯一标示符引导

      在2013年3月21日苹果已经通知开发者,从2013年5月1日起,访问UIDID的应用将不再能通过审核,替代的方案是开发者应该使用"在iOS 6中介绍的Vendor或Advertisin ...

  2. iOS中的唯一标示符

    在2013年3月21日苹果已经通知开发者,从2013年5月1日起,访问UIDIDs的程序将不再被审核通过,替代的方案是开发者应该使用"在iOS 6中介绍的Vendor或Advertising ...

  3. IOS设备唯一标示符的方案比较

    现有IOS设备唯一标示符的方案比较 UDID [[UIDevice currentDevice] uniqueIdentfier] iOS官方最早提供的UDID方案,根据某一公式,使用设备序列号.网卡 ...

  4. iOS中用到的唯一标示符

    CFUUID 从iOS2.0开始,CFUUID就已经出现了.它是CoreFoundatio包的一部分,因此API属于C语言风格.CFUUIDCreate 方法用来创建CFUUIDRef,并且可以获得一 ...

  5. linux 文件唯一标识符,详解Linux中获取全球唯一标示符UUID的方法

    UUID(Universally Unique IDentifiers),全球唯一标示符.它是一个标识系统中的存储设备的字符串,使其确定系统中的所有存储设备. 为什么要使用UUID?因为系统自动分配的 ...

  6. 创建BundleID唯一标示符App IDs(又称套装id,appid,BundleID,包名)

    http://www.applicationloader.net/blog/zh/419.html

  7. abstract 和 唯一标示符

    abstract 类和 abstract 方法 特点: abstract类不能使用new运算符创建对象 abstract类中可以有abstract方法,也可以没有(不是abstract 的类中不能写a ...

  8. android通用的UUID唯一标示符

    http://stackoverflow.com/questions/2785485/is-there-a-unique-android-device-id 版权声明:本文为博主原创文章,未经博主允许 ...

  9. 采用SAMKeychain钥匙串存储设备唯一标示与何种情况下同一个手机它存储的值会变化

    相信很多应用都会跟踪并识别设备,如何识别一台手机呢? 1.uid是唯一标识别,它是唯一硬件标示,全球不会重复: 2.你的app若开启了广告标示符选项也可以使用广告标示符号.从idfa = [[[ASI ...

最新文章

  1. 天上掉馅饼,我被砸中了!
  2. 截取指定长度html内容,并保留html格式标记
  3. Elasticsearch学习笔记-04修改数据
  4. oracle创建序列seq起始值为1_oracle 重置序列从指定数字开始的方法详解
  5. linux查看python环境变量_Linux中的Python环境变量
  6. POJ - 2492 种类并查集
  7. Fabric权限管理和策略
  8. 织梦{dede:channel}无子栏目不显示同级栏目的两种修改方法
  9. ubuntu16.04搭建ftp服务器
  10. Linux下的USB总线驱动(04)——USB键盘驱动 usbkbd.c
  11. [转]如何将WCF服务发布到IIS中去VS2010版
  12. 闪电shader_【Shader案例】怎样做出自然的闪电
  13. 安装mysql没有提示设置密码_18.04安装mysql没有提示输入密码
  14. Cisco交换机设置管理IP
  15. 在fmri研究中,cca的应用历史
  16. 试图运行项目时出错:无法启动调试 没有正确安装调试器
  17. 2. COM编程——什么是接口
  18. 51单片机学习:串口通信实验
  19. iOS微信消息延迟和不提醒,这样即可解决
  20. CSS 实现超过固定高度后出现展开折叠按钮

热门文章

  1. 数据解读 | 那些名校学霸毕业后都去了哪儿?
  2. 怎样快速在线将pdf文件转换成word
  3. 多年珍藏的55w御剑字典
  4. FOMO的量子商用黎明
  5. Python计算给定日期位于当年第几周
  6. 使用python对word文档进行另存为、合并操作处理
  7. 淘宝母婴用品分析(Excel)
  8. 捷讯fw300r虚拟服务器口号,迅捷(Fast)FW300RM迷你路由器桥接设置
  9. C++小游戏实战——生命游戏
  10. Python解题 - CSDN周赛第32期 - 运输石油(三维背包)