首页 > 编程语言 >自增主键与雪花算法的优缺点、设计更适合分库分表的UUID算法

自增主键与雪花算法的优缺点、设计更适合分库分表的UUID算法

时间:2023-11-02 20:33:23浏览次数:35  
标签:自增 分库 id 算法 分表 主键

(目录)


为什么不推荐使用自增主键

递增主键的作用

我们在数据库里保存的数据就跟excel表一样,一行行似的

image-20230617212212213

而在底层,这一行行数据,就是保存在一个个16k大小的页里。

每次都去遍历所有的行性能会不好,于是为了加速搜索,我们可以根据主键id,从小到大排列这些行数据,将这些数据页用双向链表的形式组织起来,再将这些页里的部分信息提取出来放到一个新的16kb的数据页里,再加入层级的概念。于是,一个个数据页就被组织起来了,成为了一棵B+树索引

image-20230617212254643

而当我们在建表sql里声明了PRIMARY KEY (id)时,mysql的innodb引擎,就会为主键id生成一个主键索引,里面就是通过B+树的形式来维护这套索引。

到这里,我们有两个点是需要关注的:

  • 数据页大小是固定16k
  • 数据页内,以及数据页之间,数据主键id都是从小到大排序

由于数据页大小固定了是16k,当我们需要插入一条新的数据,数据页会被慢慢放满,当超过16k时,这个数据页就有可能会进行分裂

针对B+树叶子节点如果主键是自增的,那它产生的id每次都比前一次要大,所以每次都会将数据加在B+树尾部,B+树的叶子节点本质上是双向链表,查找它的首部和尾部,时间复杂度O(1)。而如果此时最末尾的数据页满了,那创建个新的页就好。

image-20230617212409610

如果主键不是自增的,比方说上次分配了id=7,这次分配了id=3,为了让新加入数据后B+树的叶子节点还能保持有序,它就需要往叶子结点的中间找,查找过程的时间复杂度是O(lgn),如果这个页正好也满了,这时候就需要进行页分裂了。并且页分裂操作本身是需要加悲观锁的,性能变低。总体看下来,自增的主键遇到页分裂的可能性更少,因此性能也会更高。

image-20230617212515489


分库分表下的id

分库分表,那我就需要说明下,递增和自增的区别了,自增就是每次都+1,而递增则是新的id比上一个id要大就行了,具体大多少,没关系。

mysql在水平分库分表时,一般有两种方式

  1. 一种分表方式是通过对id取模进行分表,这种要求递增就好,不要求严格自增,因为取模后数据会被分散到多个分表中,就算id是严格自增的,在分散之后,都只能保证每个分表里id只能是递增的

image-20230617214050882

  1. 一种分表方式是根据id的范围进行分表(分片),它会划出一定的范围,比如以2kw为一个分表的大小,那02kw就放在这张分表中,2kw4kw放在另一张分表中,数据不断增加,分表也可以不断增加,非常适合动态扩容,但它要求id自增,如果id递增,数据则会出现大量空洞

image-20230617214059532

但不管哪种分表方式,一般是不可能继续用原来表里的自增主键的,原因也比较好理解,原来的每个表如果都从0开始自增的话,那好几个表就会出现好几次重复的id,根据id唯一的原则,这显然不合理。

所以我们在分库分表的场景下,插入的id都是专门的id服务生成的,如果是要严格自增的话,那一般会通过redis来获得,当然不会是一个id请求获取一次,一般会按批次去获得,比如一次性获得100个。快用完了再去获取下一批100个。**

但这个方案有个问题,它严重依赖redis,如果redis挂了,那整个功能就傻了

有没有不依赖于其他第三方组件的方法呢


雪花算法(Snowflake)

概念

雪花算法通过64位有特殊含义的数字来组成id

image-20230617214418964

解读:

首先第0位不用。

接下来的41位时间戳。精度是毫秒,这个大小大概能表示个69年左右,因为时间戳随着时间流逝肯定是越来越大的,所以这部分决定了生成的id肯定是越来越大的

再接下来的10位是指产生这些雪花算法的工作机器id,这样就可以让每个机器产生的id都具有相应的标识

再接下来的12位序列号,就是指这个工作机器里生成的递增数字

可以看出,只要处于同一毫秒内,所有的雪花算法id的前42位的值都是一样的,因此在这一毫秒内,能产生的id数量就是 2的10次方✖️2的12次方,大概400w,肯定是够用了,甚至有点多了。

缺点

  1. 细心的兄弟们肯定也发现了,雪花算法它算出的数字动不动就比上次的数字多个几百几万的,也就是它生成的id是趋势递增的,并不是严格+1自增的,也就是说它并不太适合于根据范围来分表的场景。这是个非常蛋疼的问题
  2. 还有个小问题是,那10位工作机器id,我每次扩容一个工作机器,这个机器怎么知道自己的id是多少呢?是不是得从某个地方读过来。
  3. 时间回拨问题
  • 雪花算法通过时间来即将作为id的区分标准之一,对于同一台id生成机器,它通过时间和序号保证id不重复
  • 机器出现问题,时间可能回到之前,此时,时间就不能区分
  • 又或者因为闰秒的出现,导致时间回拨

那有没有一种生成id生成方案,既能让分库分表能做到很好的支持动态扩容,又能像雪花算法那样并不依赖redis这样的第三方服务。


适合分库分表的uuid算法

设计:

我们可以参考雪花算法的实现,设计成下面这样。注意下面的每一位,都是十进制,而不是二进制

image-20230617215741898

解析:

  • 开头的12位依然是时间,但并不是时间戳,雪花算法的时间戳精确到毫秒,我们用不上这么细,我们改为yyMMddHHmmss,注意开头的yy是两位,也就是这个方案能保证到2099年之前,id都不会重复,能用到重复,那也是真·百年企业。同样由于最前面是时间,随着时间流逝,也能保证id趋势递增。
  • 接下来的10位,用十进制的方式表示工作机器的ip,就可以把12位的ip转为10位的数字,它可以保证全局唯一,只要服务起来了,也就知道自己的ip是多少了,不需要像雪花算法那样从别的地方去读取worker id了,又是一个小细节。
  • 在接下来的6位,就用于生成序列号,它能支持每秒钟生成100w个id。
  • 最后的4位,也是这个id算法最妙的部分。它前2位代表分库id,后2位代表分表id。也就是支持一共100*100=1w张分表。

举个例子,假设我只用了1个分库,当我一开始只有3张分表的情况下,那我可以通过配置,要求生成的uuid最后面的2位,取值只能是[0,1,2],分别对应三个表。这样我生成出来的id,就能非常均匀的落到三个分表中,这还顺带解决了单个分表热点写入的问题

如果随着业务不断发展,需要新加入两张新的表(3和4),同时第0张表有点满了,不希望再被写了,那就将配置改为[1,2,3,4],这样生成的id就不会再插入到对应的0表中。同时还可以加入生成id的概率和权重来调整哪个分表落更多数据。

有了这个新的uuid方案,我们既可以保证生成的数据趋势递增,同时也能非常方便扩展分表。非常nice。


总结

  • 建表sql里主键边上的AUTO_INCREMENT,可以让主键自增,去掉它是可以的,但这就需要你在insert的时候自己设置主键的值。
  • 建表sql里的 PRIMARY KEY 是用来声明主键的,如果去掉,那也能建表成功,但mysql内部会给你偷偷建一个 ROW_ID的隐藏列作为主键。
  • 由于mysql使用B+树索引,叶子节点是从小到大排序的,如果使用自增id做主键,这样每次数据都加在B+树的最后,比起每次加在B+树中间的方式,加在最后可以有效减少页分裂的问题。
  • 在分库分表的场景下,我们可以通过redis等第三方组件来获得严格自增的主键id。如果不想依赖redis,可以参考雪花算法进行魔改既能保证数据趋势递增,也能很好的满足分库分表的动态扩容。
  • 并不是所有数据库都建议使用自增id作为主键,比如tidb就推荐使用随机id,这样可以有效避免写热点的问题。而对于一些敏感数据,比如用户id,订单id等,如果使用自增id作为主键的话,外部通过抓包,很容易可以知道新进用户量,成单量这些信息,所以需要谨慎考虑是否继续使用自增主键

标签:自增,分库,id,算法,分表,主键
From: https://blog.51cto.com/panyujie/8154575

相关文章

  • 关于“聚类算法”
        今天我在csdn上看到一篇文章关于聚类算法的文章。我了解到聚类算法是一类无监督学习的算法,用于将数据集中的对象按照相似性进行分组或聚集。聚类算法的目标是将相似的数据点归为一类,同时将不相似的数据点分开。        常见的聚类算法包括:1.K-means聚类算法。......
  • 快速掌握SQL Server设置主键自增的方法
    SQLServer数据库中主键自增是常见的,它能够节省主键的空间,还可以实现数据的自动增加,这里主要介绍SQLServer设置主键自增的方法。 首先,在使用CREATETABLE时,可以部份设置主键自增,只需要在主键下面加上IDENTITY(n,m)关键字便可,n表示自增步长,m表示起始值,看一个示例: CREATETAB......
  • AI智能分析网关高空抛物算法如何实时检测高楼外立面剥落?
    高楼外立面剥落是一种十分危险的行为,会造成严重的人身伤害和财产损失。TSINGSEE青犀智能分析网关利用高楼外立面剥落的信息,结合高空抛物算法来进行处理就可很好解决此问题。1.数据收集首先,需要收集关于高楼外立面剥落的数据。这可以通过安装EasyCVR智能监控平台来实现,可以感......
  • 文心一言 VS 讯飞星火 VS chatgpt (126)-- 算法导论11.1 1题
    一、用go语言,假设一动态集合S用一个长度为m的直接寻址表T来表示。请给出一个查找S中最大元素的过程。你所给的过程在最坏情况下的运行时间是多少?文心一言,代码正常运行:在这个问题中,我们需要实现一个查找动态集合S中最大元素的算法,该动态集合使用一个长度为m的直接寻址......
  • TSINGSEE智慧安防:AI人员入侵检测算法的工作原理及应用场景概述
    人员入侵检测算法基于视频分析技术,自动对视频画面进行分析识别,可以对危险区的人员闯入、靠近等行为进行实时进行检测并预警,无需人工干预,协助管理者对场所的安全问题进行监管,可以广泛运用在学校、园区、工地、车站、地铁、厂区等地方。旭帆科技AI智能分析网关是基于边缘计算技术......
  • 算法开端
    算法三大特性:有穷性确定性可行性评判标准:正确性可读性健壮性效率和存储量要求表示时间复杂度的价:\(O(1)\):常数时间价\(O(n)\):线性时间价\(O(log_n)\):对数时间价\(O(nlog_n)\):线性对数时间价\(O(n^k)\):\(k\)次方时间价\(O(1)<O(log_n)<O(n)<O(nlog_n)<O(n^k)\)......
  • 浅述青犀AI算法人体攀爬行为检测的应用场景及解决方案
    人体攀爬行为检测是指利用计算机视觉技术对人类攀爬物体的行为进行识别和分析。该技术主要依靠图像和视频数据进行分析,通过识别人类身体的各个部位,以及其在攀爬过程中的动作和姿态,实现对攀爬行为的检测和跟踪。该技术的场景应用比较广泛,今天我们来介绍一下TSINGSEE青犀AI边缘计算硬......
  • 算法刷题记录-长度最小的子数组
    算法刷题记录-长度最小的子数组长度最小的子数组给定一个含有n个正整数的数组和一个正整数target。找出该数组中满足其总和大于等于target的长度最小的连续子数组[numsl,numsl+1,...,numsr-1,numsr],并返回其长度。如果不存在符合条件的子数组,返回0。示例1:输......
  • 数据结构与算法 | 哈希表(Hash Table)
    哈希表(HashTable)在二分搜索中提到了在有序集合中查询某个特定元素的时候,通过折半的方式进行搜索是一种很高效的算法。那能否根据特征直接定位元素,而非折半去查找?哈希表(HashTable),也称为散列表,就是一种数据结构,用于实现键-值对的映射关系。它通过将键映射到特定的值(哈希值)来实现......
  • 【蓝桥杯】1024 第 2 场算法双周赛(1~5)
    【蓝桥杯】1024第2场算法双周赛新生【算法赛】-蓝桥云课(lanqiao.cn)#include<iostream>usingnamespacestd;intmain(){printf("15");return0;}铺地板【算法赛】-蓝桥云课(lanqiao.cn)只要面积乘积是\(6\)的倍数即可,特判一下宽和高#include<bits/s......