天天看点

分库分表后全局ID生成方案(上)1 数据库自增id2 UUID(Universally Unique Identifier,通用唯一标识码)3 系统时间

依据数据库的第二范式,数据库中每一个表中都需要有一个唯一的主键,其他数据元素和主键一一对应。

那么关于主键的选择就成为一个关键点了,一般有如下方案:

使用业务字段作为主键

比如说对于用户表来说,可以使用手机号,email或者身份证号作为主键。对大部分场景,这并不适用,像评论表,你很难找到一个业务字段主键。而对于用户表,考虑的是业务字段是否能够唯一标识人,一人可有多个email和手机号,一旦出现变更email或手机号,就需要变更所有引用的外键信息,所以使用email或者手机作为主键不行。

身份证号码确实是用户唯一标识,但由于过于私密,并非用户系统的必须属性,你的系统如果没有要求做实名认证,肯定不会要求用户填写身份证号。

而且已有身份证号码也会变化,比如1999年时身份证号从15位变为18位,但主键一变更,以该主键为外键的表也都要随之变更,影响很大。

使用生成的唯一ID作为主键

因此,更推荐使用生成的ID作为数据库主键。不仅是因为其唯一性,且一旦生成就不会变更,可随意引用。

1 数据库自增id

提供一个专门用于生成主键的库,这样服务每次接收请求都

  1. 先往单点库的某表里插入一条没啥业务含义的数据
  2. 然后获取一个数据库自增id
  3. 取得id后,再写入对应的分库分表

优点

简单,是个人都会

缺点

因为是单库生成自增id,所以若是高并发场景,有性能瓶颈。

若硬是要改进,那就专门开个服务:

  • 该服务每次就拿到当前id最大值
  • 然后自己递增几个id,一次性返回一批id
  • 然后再把当前最大id值修改成递增几个id之后的一个值

但无论怎么说都只是基于单库。

适用场景

分库分表原因其实就俩:

  1. 单库的并发负载过高
  2. 单库的数据量过大

除非并发不高,但数据量太大导致的分库分表扩容,可用该方案,因为可能每秒最高并发最多就几百,那么就走单独的一个库和表生成自增主键即可。

并发很低,几百/s,但是数据量大,几十亿的数据,所以需要靠分库分表来存放海量数据。

当数据库分库分表后,使用自增字段就无法保证 ID 的全局唯一性了吗?

1.使用数据库的自增,设置起始值和步长不一样,不是一样可以实现吗?

2.预估每天的数据量,预先生成ID存入缓存(比如Redis)里面,然后去取,这种方法也简单?

但是这其实很难预估数据量,某一天有活动咋办?不同的起始值也可,只是增加人工成本,增加了库表咋办?忘了设置咋办?

2 UUID(Universally Unique Identifier,通用唯一标识码)

2.1 优点

本地生成,不依赖任何第三方系统,所以在性能和可用性上都比较好。

2.2 缺点

2.2.1 无序

生成的ID做好具有单调递增性,即有序。

为什么ID要有序呢?

因为在系统设计时,ID可能成为排序字段。

比如实现评论系统,一般会设计两个表:

  • 评论表

    存储评论的详细信息,其中有ID字段,有评论的内容,还有评论人ID,被评论内容的ID等等,以ID字段作为分区键

  • 评论列表

    存储着内容ID和评论ID的对应关系,以内容ID为分区键

获取内容的评论列表时,需按照时间序倒排,因为ID时间上有序,所以可按评论ID倒序排列。

若评论ID不在时间上有序,就得在评论列表中再冗余createTime列以排序,假设内容ID、评论ID和时间都8字节,就要多出50%存储空间存储时间字段,浪费存储空间。

ID有序会提升数据的写性能

MySQL InnoDB主键也是一种索引。索引数据在B+树中有序排列。当插入的下一条记录ID递增时,DV只需将其追加到后面。

但若插入数据无序,则DB查找数据应该插入的位置,再挪动该数据后面的数据,造成多余数据移动开销。

导致 B+ 树索引写时有着过多的随机写操作,而机械磁盘:

  • 随机写时,需先“寻道”找到要写入位置,即让磁头找到对应磁道,很耗时
  • 顺序写就无需寻道,大大提升索引写性能

写时不能产生有顺序的 append 操作,而需要 insert,将会读取整个 B+ 树节点到内存,在插入这条记录后会将整个节点写回磁盘,这种操作在记录占用空间较大情况下,性能下降明显

2.2.2 过长

由32个16进制数字组成的字符串,若作为DB主键使用,较耗费空间。

2.2.3 不具备业务含义

现实使用的ID中都包含有一些有意义数据,这些数据会出现在ID的固定位置。

如身份证:

  • 前6位地区编号
  • 7~14生日

    不同城市电话号码的区号不同,前三位即可看出所属运营商。

而若生成的ID可被反解,则从反解出的信息中即可验证ID,从而知道该ID生成时间、从哪个机房发号器生成、为哪个业务服务,这都有助问题排查。

Snowflake算法则可完美弥补UUID缺点。

随机生成文件名、编号等,生成Request ID标记单次请求。

3 系统时间

获取当前时间即可。但问题是高并发时,会有重复,这肯定不合适啊,而且还可能修改系统时间!

若用该方案,一般将当前时间跟很多其他的业务字段拼接起来,作为一个id。若业务上你可以接受,那也行。

你可以将别的业务字段值跟当前时间拼接起来,组成一个全局唯一的编号,比如订单编号:

时间戳 + 用户id + 业务含义编码