分库分表之后,id唯一主键如何生成

2,284 阅读3分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动

一、前言

当单库无法满足业务需求时,分库分表就是一种要做的优化手段了,然而将原本属于一个库中的数据拆分成多份后,如果让每个表的主键依旧从1开始递增,这很明显是不满足全局唯一id的需要。那这个时候我们要使用哪种方式来保证我们的是全局唯一呢,接下来花哥分享几种常见的做法,可以根据具体业务场景进行选择。

二、正文

  • 数据库自增id

此方式思路很简单,每次我们需要拿到id时,我们就向同一个库的某个表插入数据,此时我们会得到一个自增id,取到这个id之后再往对应的分库分表里去写入。同样该方式缺点也很明显,通过单库生成唯一id,不适用高并发的业务场景;

如果想要改进该方式,那么就专门开一个服务出来,这个服务每次就拿到当前id最大值,然后自己递增几个id,一次性返回批量id,然后再把当前最大id值修改成递增几个id之后的一个值;

适用场景:你分库分表就俩原因,要不就是单库并发太高,要不就是单库数据量太大;除非是你并发不高,但是数据量太大导致的分库分表扩容,你可以用这个方案,因为可能每秒最高并发最多就几百,那么就走单独的一个库和表生成自增主键即可。

缺点: 并发很低,几百/s

  • UUID

这是我们常用的手段之一,通过本地生成,不依赖数据库,但是由于uuid太长,作为主键性能太差,不适合用于主键。

适合的场景:如果你是要随机生成文件名,编号之类的

UUID.randomUUID().toString().replace("-", "")
  • snowflake算法

此方式是twitter开源的分布式id生成算法,整体思路就是把一个64位的long型的id,先看一下生成的结果:

0 | 0000011 11011101 0011000 10000110 11111001 01 | 10101 | 100000 | 0000 00000000

该结果可分为四个部分:

  • 1 bit:在二进制中,第一个bit如果是1,则表示负数,则是正数,因此该算法中第一个bit始终为0
  • 41 bit:表示的是时间戳,单位是毫秒。41 bit可以表示的数字多达2^41 - 1,也就是可以标识2 ^ 41 - 1个毫秒值,换算成年就是表示69年的时间。
  • 10 it:记录工作机器id,代表的是这个服务最多可以部署在2^10台机器上哪,也就是1024台机器。但是10 bit里5个bit代表机房id,5个bit代表机器id。意思就是最多代表2 ^ 5个机房(32个机房),每个机房里可以代表2 ^ 5个机器(32台机器)。
  • 12 bit:这个是用来记录同一个毫秒内产生的不同id,12 bit可以代表的最大正整数是2 ^ 12 - 1 = 4096,也就是说可以用这个12bit代表的数字来区分同一个毫秒内的4096个不同的id

举一个具体例子:

  • 3021-10-22 06:08:07-> 做了一些计算,再换算成一个二进制,41bit来放 -> 0000011 11011101 0011000 10000110 11111001 01
  • 机房编号,21 -> 换算成一个二进制 -> 10101
  • 机器编号,32 -> 换算成一个二进制 -> 100000