数据库主键生成策略

 


首先明确的一点是,主键是为了区分不同的行记录,所以先抛开其他的因素,主键必须要保证:唯一性(单表或者分库分表的场景下)

单表

可选的方案有:

  1. 自增id
  2. UUID
  3. 业务字段,如:手机号、身份证号等等

自增id

自增主键是单表中很常用的使用方式。阿里Java开发中规定,表必备三字段: id,gmt_create,gmt_modified。说明:其中id必为主键,类型为unsigned bigint(8个字节)、单表时自增、步长为 1(这里后面还会提到)。【1】

优点:

  1. 递增,聚集索引的性能更好
  2. 节省空间(相比于UUID、业务字段)

缺点:

  1. 不利于迁移,如将数据表迁移到其他服务器中,这种场景下解决方案:【2】
    • 目标表的表结构与原表一致,只是主键不设置为递增
    • 将原表的数据迁移到目标表中
    • 最后将主键设置为递增即可
    • ( [1] 数据迁移的过程)
    • ( [2] 迁移的过程中有数据的修改该如何处理?)
  2. 不利于扩展,如:需要做分表时不能使用自增id

总结:单表的情况下没啥毛病


UUID

UUID (Universally Unique Identifier),UUID包含:【3】

  1. 当前日期和时间,UUID的第一个部分与时间有关,如果你在生成一个UUID之后,过几秒又生成一个UUID,则第一个部分不同,其余相同
  2. 时钟序列
  3. 全局唯一的IEEE机器识别号,如果有网卡,从网卡MAC地址获得,没有网卡以其他方式获得

java.util中也提供了生成UUID的方法,生成的格式为:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12)

UUID的特点:

  1. 无序
  2. 绝大部分情况下唯一,高并发的场景下可能也会出现重复的情况

优点:

  1. 相比于自增id更加安全

缺点:

  1. 无序,所以在进行insert操作时对于性能会有影响【5】
    • 相比于按顺序写入,随机写入时需要写的page可能已经不在buffer pool中了,这样InnoDB需要将目标page再读到内存中去,产生了大量的随机IO
    • 由于写入是乱序的,InnoDB可能需要进行频繁的页分裂操作,需要分配新页,移动数据,还有可能向上引发一系列的页分裂
    • 由于频繁的页分裂,可能会造成page中的碎片过大
  2. 相对浪费空间

业务字段

如手机号,不过尽可能避免使用业务字段作为主键

缺点:

  1. 无序
  2. 可能有变化,比如有这种需求:手机号需要加密存储,且如果该字段和其他表有依赖,那么需要改很多地方

分库分表

当我们单表的数据量很大时,可能需要分库分表,那么首先要解决的问题是如何生成主键,可选的方案有:

  1. UUID
  2. 单机数据库自增ID
  3. Redis自增ID
  4. 跳跃式自增ID
  5. 雪花算法

UUID

前面提到过,并不适合做主键


单机数据库自增ID

即所有的库和表都依赖于一个某个表的自增ID,这样生成的性能瓶颈在于数据库。( [3] AUTO_INCREMENT锁机制 )


Redis自增ID

依赖Redis INCR


跳跃式自增ID

比如分十个表:tb0,tb1,...,tb9,设置初始值为:1,2,...,10,自增步长设置为10即可。

缺点:再次扩展时不好处理,除非实现预估未来多少多少年数据量不会使得10个表成为瓶颈


雪花算法

雪花算法生成的ID一共64位(2进制),最高位为保留位,后41位为时间戳(最多约69年),后10位为机器id(共1024个),最后12位为序列号(即同一个时刻可以生成4096个不同的id)

简单的代码为:

/**
 * 格式:x-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxxxx
 */
public class SnowFlake {

    private static long lastTimestamp = -1L;

    /**
     * 初始时间戳,根据业务设置
     * <p>
     * 如果没有这个初始时间戳的话,可用的时间少于69年
     */
    private static long startTimestamp;

    /**
     * 数据中心id
     */
    private static long dataCenterId;

    /**
     * 机器id
     */
    private static long workerId;

    /**
     * 序列号
     */
    private static long sequence;

    /**
     * 工作id长度为5位
     */
    private static long workerIdBits = 5L;

    /**
     * 数据中心id长度为5位
     */
    private static long dataCenterIdBits = 5L;

    /**
     * 序列号长度
     */
    private static long sequenceBits = 12L;

    /**
     * 工作id最大值
     * <p>
     * 原理:
     *   -1L的二进制为:1111....111,共64位
     *   左移5位:1111....1100000
     *   再亦或:11111
     */
    private static long maxWorkerId = -1L ^ (-1L << workerIdBits);

    /**
     * 数据中心id最大值
     */
    private static long maxDataCenterId = -1L ^ (-1L << dataCenterIdBits);

    /**
     * 序列号最大值
     */
    private static long maxSequence = -1L ^ (-1L << sequenceBits);

    /**
     * 时间戳需要左移位数 12+5+5=22位
     */
    private static long timestampLeftShift = sequenceBits + workerIdBits + dataCenterIdBits;

    /**
     * 数据id需要左移位数 12+5=17位
     */
    private static long dataCenterIdShift = sequenceBits + workerIdBits;

    /**
     * 工作id需要左移的位数,12位
     */
    private static long workerIdShift = sequenceBits;


    public synchronized static Long get() {

        long timestamp = System.currentTimeMillis();

        if (timestamp == lastTimestamp) {
            // 这里需要提前预估好,每一毫秒生成的id会不会超过4096个
            sequence = (sequence + 1) & maxSequence;
        } else {
            sequence = 0L;
        }

        lastTimestamp = timestamp;

        return ((timestamp - startTimestamp) << timestampLeftShift) |
                (dataCenterId << dataCenterIdShift) |
                (workerId << workerIdShift) |
                sequence;

    }

    public static void main(String[] args) {
        System.out.println(SnowFlake.get());
    }

}

缺点:雪花算法强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态


美团Leaf
【7】

上面的问题

  1. 待整理...
  2. 待整理...








说明

===================================

仅作为校招时的《个人笔记》,详细内容请看【参考】部分

===================================

参考

  1. https://pdai.tech/md/dev-spec/code-style/code-style-alibaba.html
  2. https://blog.csdn.net/dinghua_xuexi/article/details/106075183
  3. https://www.cnblogs.com/java-class/p/4727698.html
  4. https://www.cnblogs.com/funnyzpc/p/13541713.html
  5. 《高性能MySQL》(第3版)
  6. https://www.cnblogs.com/jajian/p/11101213.html 最后发现一篇总结的很好的博文
  7. https://tech.meituan.com/2017/04/21/mt-leaf.html
posted @   optimjie  阅读(141)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端
点击右上角即可分享
微信分享提示