MySQL 数据表主键设计,选择自增 id 还是 UUID 还是雪花 id?

在 MySQL 数据表的设计中,官方推荐我们不要使用 UUID 或者其他不连续不重复的 id,而是推荐使用连续自增的主键 id(auto_increment)。

随着现在许多项目都涉及到了分布式或者微服务,后续或多或少都会针对具体的服务需求对数据库进行拆分(分库分表),这里就会产生一个问题,拆分后的 id 该如何妥善处理?

例如,在之前的业务中,所有的数据内容都是存放在同一张数据表中的,主键 id 都是自增的,这当然没有任何问题。但是当单表的数据量上来之后我们就需要进行水平分表操作(将一张数据表的数据分成多张表),如果这时我们还是按照之前的自增形式来做主键 id,就有可能会出现 id 重复的问题。

对于高并发的环境,InnoDB 按主键进行插入时会造成明显的锁争用,主键的上界会成为争抢的热点,因为所有的插入操作都发生在这里,并发插入会导致间隙锁竞争。auto_increment 锁机制会造成自增锁的争抢并带来一定的性能消耗,如果需要改善我们可以配置 innodb_autoinc_lock_mode 参数。

分布式 id 的基本规则

  • 全局唯一性:不能出现重复的 id
  • 递增性:MySQL 的 InnoDB 使用的是聚簇索引,由于多数 RDBMS 使用 B-tree 的数据结构来存储索引数据,因此在主键的选择上我们还是应该尽可能地使用有序的主键来保证写入性能,我们保证下一个 id 一定大于上一个 id,以此来满足事务版本号、IM 增量消息或者排序的特殊需求
  • 安全性:如果 id 是连续的,那么我们在知道一些基本规则的情况下就能很轻松地推测出下一份数据,这在一些机密性较高的业务场景是很危险的。所以我们有时会希望 id 是无规则的,最好还能包含有时间戳,这样就能够在开发中快速了解这个分布式 id 的生成时间
  • 高性能高可用性:确保在任何时候都能正确地生成 id,并且在高并发的环境下也能表现良好

分布式 id 的解决方案

  • 数据表:我们可以在某个数据库中专门地去维护一张数据表,然后每次无论是哪张数据表需要自增 id,都需要去查这张表的记录,然后再利用 for update 锁表,并将取到的值加一,再把新值记录到数据表中。显然因为每次我们都需要锁表,所以这仅对于并发量小的项目而言是可以接受的

    • 优点:简单粗暴
    • 缺点:严重依赖数据库
  • Redis:因为 Redis 是单线程的,所以我们可以在 Redis 中维护一个键值对,之后无论是哪张数据表需要自增 id,都需要直接先去 Redis 中取值并加一。显然这种方式和上面单独维护一张数据表的方式是一样的,对高并发的支持都有所不足

    • 优点:灵活、不依赖数据库
    • 缺点:性能不太好
  • UUID:我们可以使用 UUID 来作为不重复的主键 id,但是 UUID 是无序的字符串,所以主键索引就会失效

    • 优点:简单、方便、性能好、全球唯一
    • 缺点:无序性、存储的是字符串、查询效率低、传输数据量大
  • 雪花算法:雪花算法是 Twitter 推出的针对分布式环境下的 id 生成算法,其结果是一个 Long 型的 64bit id。具体实现上使用 41bit 作为毫秒数,10bit 作为机器的 id(5bit 是数据中心,5bit 是机器 id),12bit 作为毫秒内的流水号(这意味着每个节点在每毫秒内可以产生 4096 个 id),最后还有一个符号位永远是 0

    • 优点:不依赖数据库、完全在内存中生成 id、高性能高可用、容量大、每秒可生成数百万个 id、id 递增、后续插入数据库的索引时性能较高
    • 缺点:严重依赖系统时钟,如果某台机器的系统时钟发生回拨,就有可能会造成 id 冲突甚至 id 乱序

数据表实验对比

新建多张字段完全相同的数据表,主键 id 类型分别使用自增 id 和 UUID,测试大批量数据读写。

结果:UUID 在数据量较大的情况下,其效率直线下滑。

索引结构对比

自增 id

自增的主键由于是顺序的,所以 InnoDB 会把每一条记录都存储在前一条记录的后面,当达到页的最大填充因子时(InnoDB 默认的最大填充因子是页大小的 15/16,它会预留出 1/16 的空间用作以后的数据修改),下一条记录就会写入到新的页中。

一旦数据按照这种顺序的方式进行加载,那么主键页就会近乎于顺序地填满,这将大大提高页的最大填充率,从而不会造成页的浪费。

此外,新插入的行一定会在原有的最大数据行的下一行,这对 MySQL 的定位和寻址很有帮助,MySQL 不必为计算出新行的位置而做出额外的消耗,并且能减少页分裂以及碎片的产生。

UUID

因为 UUID 是无序的,所以新行的值并不一定会比之前的主键值大,所以 InnoDB 无法做到总是把新行插入到索引的最后,而是需要为新行寻找到合适的位置,从而来分配新的空间(这个过程会需要做很多额外的工作,数据的毫无顺序会导致数据分布散乱)。

同时,写入的目标页很可能已经刷新到磁盘上并且已经从缓存中移除,甚至可能是还没有被加载到缓存中,以至于 InnoDB 在插入前不得不先在磁盘中读取目标页到内存中(这将伴随着大量的随机 I/O)。

又因为写入是乱序的,InnoDB 不得不频繁地做页分裂的操作,以便为新行分配空间。页分裂会导致需要移动大量的数据,一次插入操作最少需要修改三个页以上,而频繁地页分裂会导致页变得稀疏并且被不规则地填充,最终造成数据碎片。

  • 20
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
雪花算法是一种解决分布式id生成的高效方案,它生成的是一个64位的ID,可以通过8字节的long类型存放。然而,雪花算法在容器化部署时可能会遇到问题。当动态增加节点或每次部署的机器不一样时,可能会导致生成的雪花Id重复。例如,如果workerId超过了1023,那么在进行或运算时,时间戳的位置就会被影响,可能会生成重复的Id。为了解决这个问题,可以考虑使用其他方案或者对雪花算法进行适当的修改。另外,在MySQL设计表时,官方推荐使用连续自增主键id,而不是uuid或不连续不重复的雪花id。这是因为使用uuid可能会导致性能问题和索引碎片化。因此,在使用雪花算法生成id时,需要注意这些潜在的问题。 #### 引用[.reference_title] - *1* *2* [分布式id(3)— snowflake(雪花算法)的问题](https://blog.csdn.net/weixin_49698553/article/details/125873666)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [使用雪花iduuid作为Mysql主键,被媳妇怼了一顿](https://blog.csdn.net/x275920/article/details/121120866)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值