参考来源:康师傅:https://www.bilibili.com/video/BV1iq4y1u7vj?p=150
爱编程的大李子:https://blog.csdn.net/LXYDSF/article/details/126606855
一、自增ID的问题
自增 ID 做主键,简单易懂,几乎所有数据库都支持自增类型,只是实现上各自有所不同而已。自增 ID 除了简单,其他都是缺点,总体来看存在以下几方面的问题:
- 可靠性不高
存在自增ID回溯的问题,这个问题直到最新版本的MySQL 8.0才修复。
- 安全性不高
对外暴露的接口可以非常容易猜测对应的信息。比如:/User/1/ 这样的接口,可以非常容易猜测用户ID的值为多少,总用户数量有多少,也可以非常容易地通过接口进行数据的爬取。
- 性能差
自增 ID 的性能较差,需要在数据库服务器端生成。
- 交互多
业务还需要额外执行一次类似 last_insert_id() 的函数才能知道刚才插入的自增值,这需要多一次的网络交互。在海量并发的系统中,多1条SQL,就多一次性能上的开销。
- 局部唯一性
最重要的一点,自增 ID 是局部唯一,只在当前数据库实例中唯一,而不是全局唯一。
二、淘宝的主键设计(猜测)
在淘宝的电商业务中,订单服务是一个核心业务。订单表的主键 淘宝是如何设计的呢?
打开淘宝订单页面查看订单号:
# 1669344761348 2022-11-25 10:52:41
# 1550672064762 308113
1481195847180308113
1431156171142308113
1431146631521308113
订单号是 19 位的长度,且订单的最后 5 位都是一样的,都是 08113。且订单号的前面 14 位部分是单调递增的。
大胆猜测,淘宝的订单 ID 设计应该是:订单ID = 时间 + 去重字段 + 用户ID后6位尾号,这应该是之前的,现在已经改变了。
三、主键设计的一种思路
非核心业务:对应表的主键自增 ID,如告警、日志、监控等信息。
核心业务:主键设计至少应该是全局唯一且是单调递增。全局唯一保证在各系统之间都是唯一的,单调递增是希望插入时不影响数据库性能。
这里推荐最简单的一种主键设计:改造 UUID (有序)
UUID的特点:
全局唯一,占用 36 字节,数据无序,插入性能差。
认识UUID:
- 为什么UUID是全局唯一的?
- 为什么UUID占用36个字节?
- 为什么UUID是无序的?
MySQL数据库的UUID组成如下所示:
UUID = 时间 + UUID 版本(16字节)- 时钟序列(4字节) - MAC 地址(12字节)
我们以 UUID 值:e0ea12d4-6473-11eb-943c-00155dbaa39d 举例
1. 为什么UUID是全局唯一的?
在 UUID 中时间部分占用 60 位,存储的类似 TIMESTAMP 的时间戳,但表示的是从1582-10-15 00:00:00.00 到现在的 100 ns 的计数。可以看到 UUID 存储的时间精度比 TIMESTAMPE 更高,时间维度发生重复的概率降低到1/100ns。
时钟序列是为了避免时钟被回拨导致产生时间重复的可能性。MAC地址用于全局唯一。
2. 为什么UUID占用36个字节?
UUID 根据字符串进行存储,设计时还带有无用"-"字符串,因此总共需要36个字节。
3. 为什么UUID是随机无序的呢?
因为 UUID 的设计中,将时间低位放在最前面,而这部分的数据是一直在变化的,并且是无序。
改造UUID
若将时间高低位互换,则时间就是单调递增的了,也就变得单调递增了。MySQL 8.0 可以更换时间低位和时间高位的存储方式,这样 UUID 就是有序的UUID了。
MySQL 8.0 还解决了 UUID 存在的空间占用的问题,除去了 UUID 字符串中无意义的"-"字符串,并且将字符串用二进制类型保存,这样存储空间降低为了16字节。
可以通过 MySQL 8.0 提供的 uuid_to_bin 函数实现上述功能,同样的,MySQL 也提供了 bin_to_uuid 函数进行 转化:
SET @uuid = UUID();
SELECT @uuid,uuid_to_bin(@uuid),uuid_to_bin(@uuid,TRUE);
通过函数 uuid_to_bin(@uuid,true)
将 UUID 转化为有序 UUID 了。全局唯一 + 单调递增,这不就是我们想要的主键!
如果不是 MySQL8.0 肿么办?
手动赋值字段做主键!
比如,设计各个分店的会员表的主键,因为如果每台机器各自产生的数据需要合并,就可能会出现主键重复的问题。
可以在总部 MySQL 数据库中,有一个管理信息表,在这个表中添加一个字段,专门用来记录当前会员编号的最大值。
门店在添加会员的时候,先到总部 MySQL 数据库中获取这个最大值,在这个基础上加 1,然后用这个值作为新会员的“id”同时,更新总部 MySQL 数据库管理信息表中的当前会员编号的最大值。
这样一来,各个门店添加会员的时候,都对同一个总部 MySQL 数据库中的数据表字段进行操作,就解决了各门店添加会员时会员编号冲突的问题。
四、雪花算法(SnowFlake)
SnowFlake是Twitter公司采用的一种算法,目的是在分布式系统中产生全局唯一且趋势递增的ID。
组成部分(64bit)
1.第一位 占用1bit,其值始终是0,没有实际作用。 2.时间戳 占用41bit,精确到毫秒,总共可以容纳约69年的时间。 3.工作机器id 占用10bit,其中高位5bit是数据中心ID,低位5bit是工作节点ID,做多可以容纳1024个节点。 4.序列号 占用12bit,每个节点每毫秒0开始不断累加,最多可以累加到4095,一共可以产生4096个ID。
SnowFlake算法在同一毫秒内最多可以生成多少个全局唯一ID呢:: 同一毫秒的ID数量 = 1024 X 4096 = 4194304