现如今越来越多的公司都在用分布式、微服务,那么对应的就会针对不同的服务进行数据库拆分,然后当数据量上来的时候也会进行分表,那么随之而来的就是分表以后id的问题。
例如之前单体项目中一个表中的数据主键id都是自增的,mysql是利用autoincrement来实现自增,而oracle是利用序列来实现的,但是当单表数据量上来以后就要进行水平分表,阿里java开发建议是单表大于500w的时候就要分表,但是具体还是得看业务,如果索引用的号的话,单表千万的数据也是可以的。 水平分表就是将一张表的数据分成多张表,那么问题就来了如果还是按照以前的自增来做主键id,那么就会出现id重复,这个时候就得考虑用什么方案来解决分布式id的问题了。
自增主键是数据库根据插入数据库行,按顺序自动赋予的一个连续的值。这种形式的主键本身是没有什么问题的,在学校做个毕业设计或者在创业型公司使用是没什么问题的。但是你是在大型公司(淘宝、京东等电商项目)分布式情况下使用是有严重问题的。接下来我们进行分析:
案例:如下有个商品表,里面有三亿数据,数据库表有三个分片
假设我们按照自增主键方式进行分片存储 0-1 在分片 1、1-2 在分片 2、2-3 在分片 3,按照设计来看的话没什么问题,但实际有非常严重的问题,接下来进行讲解
a . 资源浪费
还是如上图,分表在三个数据库分别有三个表分片,你怎么知道那个数据库中的分片,可以容纳 1 亿数据呢,这是不是猜的呢
也行某些一行记录数据量比较小,在某个分片上可以存储 1.5 亿条数据呢,那还能再扩展呢?答案是肯定不能的,应为设计的时候依据按照范围分片的方式已经设计好的。且自增主键必须连续只能采用”范围分片”形式
b. 尾部热点
自增主键分片又会带来一个附加的效应技术尾部热点,这是由范围分片自身的特定决定的。
比如说,现在我们的商品数据已经堆积到 2.5 亿,新的数据在插入的时候,所有的数据都被插入到了分片3中,之前的分片 1 2 都不会有写入操作,所以表分片3中库的压力是非常大的。
当然,对于这样的情况也是有解决方案的。即 HASH 法,这种情况的分片相比范围法效率上高1.5倍之多。
面对自增主键产生的问题,有些公司会使用 UUID 或 GUID 的主键。使用UUID可以替代自增主键吗?答案是,不可以!为什么呢,接下来我们进行讲解:
首先 UUID ,他是 128 位长的且是无序的,这种无序的字符串不能作为我们的主键呢,这样从 MySQL的 InnoDB(B+数)引擎数据库底层机制说起。使用UUID是无序的,作为主键会涉及大量页分裂
可以在某个库中专门维护表,然后每次无论哪个表需要自增id的时候都去查这个表的记录,然后用for update锁表,然后取到的值加一,然后返回以后把再把值记录到表中,但是这个方法适合并发量比较小的项目,因此每次都得锁表。
因为redis是单线程的,可以在redis中维护一个键值对,然后哪个表需要直接去redis中取值然后加一,但是这个跟上面一样由于单线程都是对高并发的支持不高,只适合并发量小的项目。
雪花算法(Snowflake)是 Twitter 公司分布式项目采用的 ID 生成算法。他呢主要是根据时间顺序,结合机器ID与序列,生成一个定长的数字。
这个数字就可以作为数据库的主键,他既可以保证是连续的也可以保证在分布式系统中是唯一的。
Snowflake 每毫秒可以生成 416 万个 ID,这对于我们在分布式系统中使用的场景已经足够了。