视频可看: 动画讲解:为什么不能使用自增ID或者UUID做MySQL的主键,雪花算法生成的主键存在哪些问题_哔哩哔哩_bilibili
一、MySQL分布式架构中,为什么不能使用自增id作为主键
自增主键的好处:写入效率高
弊端:只适合单机数据库,不能用于分库分表的情况,会产生重复id
1. 如果基于主键范围分片(范围固定,不容易动态调整,写满一个才写下一个),压力集中在某个分片上(尾部热点),无法做到负载均衡。可以采用其他业务字段 hash 分片,能缓解压力。
2. 如果使用AUTO INCREMENT配合replace into自增 ID,那么每当插入数据时,都会占用自增锁和插入锁。
3. 在分布式系统中,每个节点都可能有自己的自增id,在合并数据的时候,可能会出现数据冲突。
4. 在实际开发中,有时需要将两张数据表合并或迁移如果某两张表中都有有增id,就会出现主键冲突。
5. 多线程写时,自增id可能会出问题。
变通方式:不使用自增主键做数据分片,而使用业务数据的用户ID做数据分片,每个用户的数据都集中在一个库,可以做到更加的内聚。
二、UUID可以用来做主键吗?存在哪些问题?
UUID.randomUUID().toString()
UUID随机、无序,具有非常好的全局唯一性,但不推荐做MySQL主键。从MySQL的B+树原理看,一个page写满向下一个page写,要求下一个page的数据要大于上一个page的数据,现插入一个新数据,但UUID是随机无序的,这就很有可能导致数据的移动,根页指针的调整。插入一条数据,却伴随着几次数据移动,这是不合适的。
UUID是由32个十六进制数字和 4个连字符组成的,不太容易阅读、查询效率不高且浪费空间。UUID是随机生成的,没有任何规律和顺序可言。当插入新记录时,这可能会导致索引分裂和磁盘碎片,并影响查询性能。
好像可以使用有序UUID,主键有序,B+树只需在后面追加记录。
三、雪花算法(SnowFlake)生成主键,请你描述雪花算法的原理,能介绍一下它有哪些优势和不足呀,该如何解决这些不足呢?
雪花算法是一种分布式ID生成算法,用来保证在大规模分布式系统中生成全局唯一的ID,满足唯一性和有序性,避免了分布式系统环境下的ID冲突;并且生成ID的过程中无需依赖数据库等外部系统,减少了系统复杂性。
雪花算法是完全基于时间戳的递增而生成的,可以实现分布式部署。
优点:
(1)高性能高可用:生成时不依赖于数据库,完全在内存中生成。
(2)容量大:每秒中能生成数百万的自增ID。
(3)ID自增:存入数据库中,索引效率高。
缺点:
(1)严重依赖服务器的时钟,如果发生时钟回拨,就会出现时间重复,导致生成重复ID。
(2)41bit可存储的时间跨度是69年(从1970年算起),快不够用了。——>折中方法:时间戳减去系统上线的时间
64bit:
1bit:不使用
41bit:时间戳(从1970-01-01 8:00)
10bit-标识位:5bit数据中心id + 5bit机器号id
12bit-序列号:用来记录同毫秒内产生的不同id
雪花算法生成 ID 冲突问题:
1. 前提条件:
- 服务通过集群的方式部署,其中部分机器标识位一致。
- 业务存在一定的并发量,没有并发量无法触发重复问题。
- 生成 ID 的时机:同一毫秒下的序列号一致。
2. 标识位如何定义才能不重复?
有两种方案:预分配和动态分配。
(1). 预分配(静态)
应用上线前,统计当前服务的节点数,人工去申请标识位。
这种方案,没有代码开发量,在服务节点固定或者项目少可以使用,但是解决不了服务节点动态扩容性问题。
(2). 动态分配
将标识位存放在 Redis、Zookeeper、MySQL 等中间件,在服务启动的时候去请求标识位,请求后标识位并将其更新为下一个可用的。
通过存放标识位,延伸出一个问题:雪花算法的 ID 是 服务内唯一还是全局唯一。
以 Redis 举例,如果要做服务内唯一,存放标识位的 Redis 节点使用自己项目内的就可以;如果是全局唯一,所有使用雪花算法的应用,要用同一个 Redis 节点。
两者的区别仅是 不同的服务间是否公用 Redis。如果没有全局唯一的需求,最好使 ID 服务内唯一,因为这样可以避免单点问题。
服务的节点数超过 1024,则需要做额外的扩展;可以扩展 10 bit 标识位,或者选择开源分布式 ID 框架。
动态分配实现方案:Redis 存储一个 Hash 结构 Key,包含两个键值对:dataCenterId 和 workerId。
雪花算法不是万能的,并不能适用于所有场景。如果 ID 要求全局唯一并且服务节点超出 1024 节点,可以选择修改算法本身的组成,即扩展标识位,或者选择开源方案:美团LEAF、百度UID。