关于数据表主键的设计,团队内部产生过这样的争论:
举例:订单表业务相关的字段有
字段 | 说明 |
---|---|
amount | 流水号 |
sn | 流水号 |
pay_sn | 支付流水号 |
buyer | 购买人 |
ship | 物流相关数据 |
create_at | 订单生成时间 |
可以确定的是,sn 是唯一的,所以 A 的观点是 “用 sn 做为表的主键”,与其不同的观点是 “添加业务无关的 id 自增字段用作主键”。这两种设计哪个更合理呢?
简而言之,就是选择业务主键还是代理主键的问题。
- 业务主键:在数据库表中把具有业务逻辑含义的字段作为主键;
- 代理主键:在数据库表中使用业务逻辑无关的字段作为主键;
代理主键优点有什么?
1. 不受业务变更影响
任何有业务含义的列都有改变的可能性。关系数据库学的最重要的一个理论就是:不要给关键字赋予任何业务意义。假如关键字具有了业务意义,当用户决定改变业务含义,也许他们想要为关键字增加几位数字或把数字改为字母,那么就必须修改相关的关键字。一个表中的主关键字有可能被其他表作为外键。就算是一个简单的改变,譬如在客户号码上增加一位数字,也可能会造成极大的维护上的开销。
如果以订单流水号为主键,其数据类型是字符型,数据生成规则是“日期+时间+4位随机数”。以后的某次迭代,觉得这样的流水保密性差,会让人嗅探到日订单数,所以数据类型改为整型,生成规则改为内部、不公开的数字规则。这样的改动,其它与订单表关联的表的外键都需要全部改一遍。
2. 存储占用空间小
int 是4字节,bigint 是8字节,如果有其它关联表,这些表也可节省空间
3. 联表查询效率高
数据库处理整型字段的连接查询,效率是最高的。
4. 索引和排序效率高
主键是按顺序自增的,确保数据的顺序插入,对于检索非常有利
5. 防止录入错误
比如某表以唯一的手机号作为主键,后端管理人员将手机号录入错误,当其它表已经关联了该表后,再修改主键,需要同时更新关联表
6. 防止删除后重现
由于代理主键是无意义自增字段,所以能保证不会再出现同一个主键值,业务相关的主键则无法避免。比如某表以手机号为主键,然后用户甲用手机号 A 注册,一段时间后注销了账号,同时也在电信部门注销了手机号 A。当用户乙获得手机号 A 并用来注册用户时,系统会面临已关联过手机号 A 的用户甲数据如何妥善处理的问题。
业务主键优点有什么?
1. 减少一个业务无关字段
2. 避免表关联关系丢失
假如银行系统用 id 做主键,一旦发生故障,id 字段被错误修改了,其它表与其关联关系会丢失。所以通常银行系统都要求使用业务主键,身份证号、存折号、卡号等,这个需求并不是出于性能的考虑而是出于安全性的考虑。
其实吧,上面的说法是指没有为表建立关系约束的情景,如果添加了外键约束,主键的 update 和 delete 操作是不会造成表关系丢失的。
如何选择?
目前比较推荐的做法有两种
1. 使用业务无关字段做主键
直接使用与业务无关的自增 id 作为主键
2. 使用业务无关字段做主键,同时添加逻辑主键
使用自增 id 作为主键应对效率问题;同时采用 uuid 做逻辑主键可以用来应对之后的水平分表。
对于我们相对小型的数据库设计,选择第1种做法。