主键设计
一个很明显的错误:每次都必须用8字节的 BIGINT 做主键,而不要用INT。 错!
自增ID的问题
自增ID做主键,简单易懂,几乎所有数据库都支持自增类型,只是实现上各自有所不同而已。自增ID除了简单,其他都是缺点,总体来看存在以下几方面的问题:
- 可靠性不高
存在自增ID回溯的问题,这个问题直到最新版本的MySQL 8.0才修复。
- 安全性不高
对外暴露的接口可以非常容易猜测对应的信息。比如:/User/1/这样的接口,可以非常容易猜测用户ID的值为多少,总用户数量有多少,也可以非常容易地通过接口进行数据的爬取。
- 性能差
自增ID的性能较差,需要在数据库服务器端生成。
- 交互多
业务还需要额外执行一次类似 last_insert_id() 的函数才能知道刚才插入的自增值,这需要多一次的网络交互。在海量并发的系统中,多1条SQL,就多一次性能上的开销。
- 局部唯一性
最重要的一点,自增ID是局部唯一,只在当前数据库实例中唯一,而不是全局唯一(在任意服务器间都是唯一的)。对于目前分布式系统来说,这简直就是噩梦。
业务字段做主键的问题

比如用卡号作为主键,实际情况是, 会员卡号可能存在重复使用 的情况。比如,张三因为工作变动搬离了原来的地址,退还了会员卡。但是,商家不想让这个会员卡空着,就把卡号是 “10000001” 的会员卡发给了王五。
比如身份证号作为主键,可问题是,身份证号属于个人隐私 ,顾客不一定愿意给你。要是强制要求会员必须登记身份证号,会把很多客人赶跑的。其实,客户电话也有这个问题,这也是我们在设计会员信息表的时候,允许身份证号和电话都为空的原因。
所以,建议尽量不要用跟业务有关的字段做主键。毕竟,作为项目设计的技术人员,谁也无法预测在项目的整个生命周期中,哪个业务字段会因为项目的业务需求而有重复,或者重用之类的情况出现。
淘宝的主键设计
#订单号1550672064762308113148119584718030811314311561711423081131431146631521308113订单ID = 时间 + 去重字段 + 用户ID后6位尾号
推荐的主键设计

对于核心业务,推荐使用UUID进行主键设计。
UUID特点:全局唯一,占用36字节,数据无序,插入性能差。
UUID = 时间+UUID版本(16字节)- 时钟序列(4字节) - MAC地址(12字节)

为什么UUID是全局唯一的:
在UUID中时间部分占用60位,存储的类似TIMESTAMP的时间戳,但表示的是从1582-10-15 00:00:00.00到现在的100ns的计数。可以看到UUID存储的时间精度比TIMESTAMPE更高,时间维度发生重复的概率降低到1/100ns。
时钟序列是为了避免时钟被回拨导致产生时间重复的可能性。MAC地址用于全局唯一。
为什么UUID占用36个字节:
UUID根据字符串进行存储,设计时还带有无用”-“字符串,因此总共需要36个字节。
为什么UUID是随机无序的呢:
因为UUID的设计中,将时间低位放在最前面,而这部分的数据是一直在变化的,并且是无序。
改造UUID:
若将时间高低位互换,则时间就是单调递增的了,也就变得单调递增了。MySQL 8.0可以更换时间低位和时间高位的存储方式,这样UUID就是有序的UUID了。
MySQL 8.0还解决了UUID存在的空间占用的问题,除去了UUID字符串中无意义的”-“字符串,并且将字符串用二进制类型保存,这样存储空间降低为了16字节。
可以通过MySQL8.0提供的uuid_to_bin函数实现上述功能,同样的,MySQL也提供了bin_to_uuid函数进行转化:
SET @uuid = UUID();SELECT @uuid,uuid_to_bin(@uuid),uuid_to_bin(@uuid,TRUE);

