MySQL之主键ID⽣成策略
5种全局ID⽣成⽅式、优缺点及改进⽅案
全局唯⼀id特点:
全局唯⼀性:不能出现重复的ID号,既然是唯⼀标识,这是最基本的要求;
趋势递增:在MySQL InnoDB引擎中使⽤的是聚集索引,由于多数RDBMS使⽤B-tree的数据结构来存储索引数据,在主键的选择上⾯我们应 该尽量使⽤有序的主键保证写⼊性能;
单调递增:保证下⼀个ID⼀定⼤于上⼀个ID,例如事务版本号、IM增量消息、排序等特殊需求;
信息安全:如果ID是连续的,恶意⽤户的扒取⼯作就⾮常容易做了,直接按照顺序下载指定URL即可;如果是订单号就更危险了,竞对可以直接知道我们⼀天的单量。所以在⼀些应⽤场景下,会需要ID⽆规则、不规则;
⾼可⽤性:同时除了对ID号码⾃⾝的要求,业务还对ID号⽣成系统的可⽤性要求极⾼,想象⼀下,如果ID⽣成系统瘫痪,这就会带来⼀场灾难。所以不能有单点故障;
分⽚⽀持:可以控制ShardingId。⽐如某⼀个⽤户的⽂章要放在同⼀个分⽚内,这样查询效率⾼,修改也容易;
长度适中。
1、数据库⾃增长序列或字段⽣成id
最常见的⼀种⽣成id⽅式。利⽤数据库本⾝来进⾏设置,在全数据库内保持唯⼀。
【优点】
⾮常简单。利⽤现有数据库系统的功能实现,成本⼩,代码简单,性能可以接受。ID号单调递增。可以实现⼀些对ID有特殊要求的业务,⽐如对分页或者排序结果这类需求有帮助。
【缺点】
强依赖DB:不同数据库语法和实现不同,数据库迁移的时候、多数据库版本⽀持的时候、或分表分库的时候需要处理,会⽐较⿇烦。当DB 异常时整个系统不可⽤,属于致命问题。
单点故障:在单个数据库或读写分离或⼀主多从的情况下,只有⼀个主库可以⽣成。有单点故障的风险。
数据⼀致性问题。配置主从复制可以尽可能的增加可⽤性,但是数据⼀致性在特殊情况下难以保证。主从切换时的不⼀致可能会导致重复发号。
难于扩展:在性能达不到要求的情况下,⽐较难于扩展。ID发号性能瓶颈限制在单台MySQL的读写性能。
【部分优化⽅案】
针对主库单点, 如果有多个Master库,则每个Master库设置的起始数字不⼀样,步长⼀样,可以是M
aster的个数。⽐如:Master1 ⽣成的是 1,4,7,10,Master2⽣成的是2,5,8,11 Master3⽣成的是 3,6,9,12。这样就可以有效⽣成集中的唯⼀ID,也可以⼤⼤降低ID⽣成数据库操作的负载。
mysql下载32位2、UUID
常见的⽣成id⽅式,利⽤程序⽣成。
UUID (Universally Unique Identifier) 的⽬的,是让分布式系统中的所有元素,都能有唯⼀的辨识资讯,⽽不需要透过中央控制端来做辨识资讯的指定。如此⼀来,每个⼈都可以建⽴不与其它⼈冲突的 UUID。在这样的情况下,就不需考虑数据库建⽴时的名称重复问题。
UUID的标准形式包含32个16进制数字,以连字号分为五段,形式为8-4-4-4-12的36个字符,⽰例:550e8400-e29b-41d4-a716-446655440000,到⽬前为⽌业界⼀共有5种⽅式⽣成UUID。
在Java中我们可以直接使⽤下⾯的API⽣成UUID:
String s = UUID.randomUUID().toString();
【优点】
⾮常简单,本地⽣成,代码⽅便,API调⽤⽅便。
性能⾮⾼。⽣成的id性能⾮常好,没有⽹络消耗,基本不会有性能问题。
全球唯⼀。在数据库迁移、系统数据合并、或者数据库变更的情况下,可以 从容应对。
【缺点】
存储成本⾼:UUID太长,16字节128位,通常以36长度的字符串表⽰,很多场景不适⽤。如果是海量数据库,就需要考虑存储量的问题。
信息不安全:基于MAC地址⽣成UUID的算法可能会造成MAC地址泄露,这个漏洞曾被⽤于寻梅丽莎病毒的制作者位置。
不适⽤作为主键:ID作为主键时在特定的环境会存在⼀些问题,⽐如做DB主键的场景下,UUID就⾮常不适⽤。UUID往往是使⽤字符串存储,查询的效率⽐较低。
UUID是⽆序的:不是单调递增的,⽽现阶段主流的数据库主键索引都是选⽤的B+树索引,对于⽆序长度过长的主键插⼊效率⽐较低。
传输数据量⼤。
【部分优化⽅案】
为了解决UUID不可读, 可以使⽤UUID to Int64的⽅法 。
为了解决UUID⽆序的问题, NHibernate在其主键⽣成⽅式中提供了Comb算法(combined guid/timestamp)。保留GUID的10个字节,⽤另6个字节表⽰GUID⽣成的时间(DateTime)。
3、Redis⽣成ID
当使⽤数据库来⽣成ID性能不够要求的时候,我们可以尝试使⽤Redis来⽣成ID。这主要依赖于Redis是单线程的,所以也可以⽤⽣成全局唯⼀的ID。可以⽤Redis的原⼦操作 INCR和INCRBY来实现。
可以使⽤Redis集来获取更⾼的吞吐量。假如⼀个集中有5台Redis。可以初始化每台Redis的值分别是1,2,3,4,5,然后步长都是5。各个Redis⽣成的ID为:
A:1,6,11,16,21
B:2,7,12,17,22
C:3,8,13,18,23
D:4,9,14,19,24
E:5,10,15,20,25
这个负载到哪台机器上需要提前设定好,未来很难做修改。但是3-5台服务器基本能够满⾜,都可以获得不同的ID。步长和初始值⼀定需要事先设定好。使⽤Redis集也可以防⽌单点故障的问题。
⽐较适合使⽤Redis来⽣成⽇切流⽔号。⽐如订单号=⽇期+当⽇⾃增长号。可以每天在Redis中⽣成⼀个Key,使⽤INCR进⾏累加。
【优点】
不依赖于数据库,灵活⽅便,且性能优于数据库。。
数字ID天然排序,对分页或者需要排序的结果很有帮助。
【缺点】
如果系统中没有Redis,还需要引⼊新的组件,增加系统复杂度。。
需要编码和配置的⼯作量⽐较⼤。
Redis单点故障,影响序列服务的可⽤性。
4、zookeeper⽣成ID
zookeeper主要通过其znode数据版本来⽣成序列号,可以⽣成32位和64位的数据版本号,客户端可以使⽤这个版本号来作为唯⼀的序列号。
很少会使⽤zookeeper来⽣成唯⼀ID。主要是由于需要依赖zookeeper,并且是多步调⽤API,如果在竞争较⼤的情况下,需要考虑使⽤分布式锁。因此,性能在⾼并发的分布式环境下,也不甚理想。
5、Twitter的snowflake算法
snowflake(雪花算法)是Twitter开源的分布式ID⽣成算法,结果是⼀个long型的ID。这种⽅案把64-bit分别划分成多段,分开来标⽰机器、时间等。如图:
其核⼼思想是:使⽤41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中⼼,5个bit的机器ID),12
bit作为毫秒内的流⽔号(意味着每个节点在每毫秒可以产⽣ 4096 个 ID),最后还有⼀个符号位,永远是0。具体实现的代码可以参看github。
snowflake算法可以根据⾃⾝项⽬的需要进⾏⼀定的修改。⽐如估算未来的数据中⼼个数,每个数据中⼼的机器数以及统⼀毫秒可以能的并发数来调整在算法中所需要的bit数。
【优点】
稳定性⾼,不依赖于数据库等第三⽅系统,以服务的⽅式部署,稳定性更⾼,⽣成ID的性能也是⾮常⾼的。
灵活⽅便,可以根据⾃⾝业务特性分配bit位。
单机上ID单调⾃增,毫秒数在⾼位,⾃增序列在低位,整个ID都是趋势递增的。
【缺点】
强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可⽤状态。
ID可能不是全局递增。在单机上是递增的,但是由于涉及到分布式环境,每台机器上的时钟不可能完全同步,也许有时候也会出现不是全局递增的情况。
相关链接:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。