• home > webfront > server > NestJS >

    mysql主键id/uid抉择:从 UUID 到 UUIDv7演化之路

    Author:zhoulujun Date:

    直接让id主键存储 uid 字符串,或者 使用直接把id 改为uid,是否ok呢?user_auto_key,user_uuid,user_random_key 又有何区别呢?

    比如我这个网站系统的,文章与栏目都是id,因为mysql 自增id太好使

    但是,比如微信等,经常看到openID 与UID等信息,因为出于安全等考虑,不让用户通过id 自增,去定向爆破

    这个时候资源对外id,业务上统一使用uid 或者bizId(businessID)

    id存储uid 或者把id字段改为uid

    那么这数据库层,直接让id主键存储 uid 字符串,或者 使用直接把id 改为uid,是否ok呢?

    理论是是可以的(实际可以,就不用看下文了!)

    就这样,我直接使用uid作为主键,更加方便明了

    如何生成UID

    mysql自带呀——简单便捷:直接在SQL语句中使用UUID(),无需额外代码。而且可以设置默认值呀!

    mysql 生成uid函数

    MySQL 8.0以上或者MariaDB 10.8以上,使用UUID_TO_BIN(UUID(), 1)可将时间戳前置,生成二进制有序UUID,显著提升索引性能。

    CREATE TABLE example (
      uid BINARY(16) PRIMARY KEY DEFAULT UUID_TO_BIN(UUID(), 1),
    );

    这样就okay了,但是,如过你真的在生产上这么搞,稍微正规点的团队,老板都要屌死你——由于 UUID 值的大小和未排序,使用 UUID 值可能会导致性能问题。

    使用uuid和自增id的索引结构对比

    使用自增id的内部结构

    自增的主键的值是顺序的,所以Innodb把每一条记录都存储在一条记录的后面。当达到页面的最大填充因子时候(innodb默认的最大填充因子是页大小的15/16,会留出1/16的空间留作以后的修改):

    1. 下一条记录就会写入新的页中,一旦数据按照这种顺序的方式加载,主键页就会近乎于顺序的记录填满,提升了页面的最大填充率,不会有页的浪费

    2. 新插入的行一定会在原有的最大数据行下一行,mysql定位和寻址很快,不会为计算新行的位置而做出额外的消耗

    3. 减少了页分裂和碎片的产生

    v2-ae66b8e80eb47814d7562e2d34dfe8e2_1440w.jpg

    • 别人一旦爬取你的数据库,就可以根据数据库的自增id获取到你的业务增长信息,很容易分析出你的经营情况

    • 对于高并发的负载,innodb在按主键进行插入的时候会造成明显的锁争用,主键的上界会成为争抢的热点,因为所有的插入都发生在这里,并发插入会导致间隙锁竞争

    • Auto_Increment锁机制会造成自增锁的抢夺,有一定的性能损失——Auto_increment的锁争抢问题,如果要改善需要调优innodb_autoinc_lock_mode的配置

    使用uuid的索引内部结构

    因为uuid相对顺序的自增id来说是毫无规律可言的,新行的值不一定要比之前的主键的值要大,所以innodb无法做到总是把新行插入到索引的最后,而是需要为新行寻找新的合适的位置从而来分配新的空间。这个过程需要做很多额外的操作,数据的毫无顺序会导致数据分布散乱,将会导致以下的问题:

    1. 写入的目标页很可能已经刷新到磁盘上并且从缓存上移除,或者还没有被加载到缓存中,innodb在插入之前不得不先找到并从磁盘读取目标页到内存中,这将导致大量的随机IO

    2. 因为写入是乱序的,innodb不得不频繁的做页分裂操作,以便为新的行分配空间,页分裂导致移动大量的数据,一次插入最少需要修改三个页以上

    3. 由于频繁的页分裂,页会变得稀疏并被不规则的填充,最终会导致数据会有碎片

    在把随机值(uuid和雪花id)载入到聚簇索引(innodb默认的索引类型)以后,有时候会需要做一次OPTIMEIZE TABLE来重建表并优化页的填充,这将又需要一定的时间消耗。

    对于此,有蛮多的测试案例,关于

    user_auto_key,user_uuid,user_random_key,


    从 UUID 到 UUIDv7

    UUID 不断发展,以满足对时间敏感的应用需求。最常用的版本包括:

    • UUIDv1利用时间和节点信息,包含时间戳和 MAC 地址。虽然能有效保证唯一性,但由于 MAC 地址会暴露敏感信息,因此会带来隐私问题。

    • UUIDv4随机生成,提供了简单性和隐私性,但代价是潜在的(尽管极不可能发生)碰撞。该版本被广泛应用于顺序排列并不重要的场合。

    • UUIDv3 和 UUIDv5:利用哈希算法(v3 使用 MD5,v5 使用 SHA-1)从命名空间标识符和名称推导出 UUID,确保相同输入的结果具有确定性。

    RFC 9562 中引入的更新版本带来了重大改进:

    • UUIDv6:v1 的重组版本,具有更强的私密性,并针对时间顺序排序进行了优化。

    • UUIDv7:旨在提供基于时间的顺序排序,是数据库索引和分布式系统的理想选择。

    • UUIDv8:允许自定义应用特定元数据字段,提供无与伦比的灵活性。

    UUIDv7 解决了早期版本的主要缺陷,尤其是在数据库索引和分布式系统方面。通过使用有时间顺序的结构,可以确保:

    • 高效索引:基于时间的顺序排列减少了数据库索引中的碎片,从而提高了查询性能。

    • 高可扩展性:适用于需要唯一、有序标识符的分布式环境。

    • 隐私性:避免包含 MAC 地址等敏感信息。

    UUIDv8 引入了一项突破性功能:针对特定应用需求的自定义位。该版本允许在 UUID 中直接嵌入元数据,使其具有很强的适应性:

    • 物联网设备:嵌入设备特定信息

    • 跨系统数据传输:包含上下文元数据,便于跟踪。

    • 自定义应用:根据特定领域需求定制 UUID。


    版本构造方式主要功能用例
    v1时间 + MAC地址高唯一性, 隐私问题传统系统, 内部工具
    v4随机简单, 高隐私性Web应用, 通用功能
    v6基于时间(重构的)有序, 隐私增强现代数据库
    v7时间有序(RFC 9562)索引优化分布式系统, 日志
    v8自定义字段高灵活性IoT, 特定应用

    这些,目前的库都很成熟了,比如:https://www.npmjs.com/package/uuid


    超越 UUID:替代方案与灵感

    • ULID:将基于时间戳的排序与随机性相结合,确保单调性。

    • Snowflake:由 Twitter 提出,包含时间戳、机器 ID 和序列号。

    • KSUID:为分布式系统优化的 K 排序唯一标识符。

    虽然这些替代方案在特定情况下很有效,但 UUID 为大部分应用提供了标准化、跨平台的解决方案


    参考文章:
    从 UUID 到 UUIDv7:唯一标识符的演进 https://zhuanlan.zhihu.com/p/16649819723



    转载本站文章《mysql主键id/uid抉择:从 UUID 到 UUIDv7演化之路》,
    请注明出处:https://www.zhoulujun.cn/html/webfront/server/nestjs/9537.html