# 前言

表主键的设计在实际的开发中经常用,oracle里边都习惯使用UUID,mysql里边都比较习惯使用自增id,hbase的表id的设计非常重要,这关系到查询的效率以及排序问题,所以表主键id的设计极其重要。对于业务量比较大的设计也需要长远的考虑,是否后期会用到分库分表的策略,如果分库分表主键id是否会重复等。来学习一波数据库表主键id的设计。

1.UUID

UUID 是由一组32位数的16进制数字所构成,以连字号分隔的五组来显示,形式为 8-4-4-4-12,总共有 36个字符。

java中util工具类提供了UUID工具类

/**
     * Static factory to retrieve a type 4 (pseudo randomly generated) UUID.
     *
     * The {@code UUID} is generated using a cryptographically strong pseudo
     * random number generator.
     *
     * @return  A randomly generated {@code UUID}
     */
    public static UUID randomUUID() {
        SecureRandom ng = Holder.numberGenerator;

        byte[] randomBytes = new byte[16];
        ng.nextBytes(randomBytes);
        randomBytes[6]  &= 0x0f;  /* clear version        */
        randomBytes[6]  |= 0x40;  /* set to version 4     */
        randomBytes[8]  &= 0x3f;  /* clear variant        */
        randomBytes[8]  |= 0x80;  /* set to IETF variant  */
        return new UUID(randomBytes);
    }

测试UUID:

public class UUIDTest {
    public static void main(String[] args) {
        System.out.println(UUID.randomUUID().toString());
        System.out.println(UUID.randomUUID().toString());
        System.out.println(UUID.randomUUID().toString());
        System.out.println(UUID.randomUUID().toString());
//        6c736056-cdb4-42b8-a40c-cda25f81b520
//        c315c255-c932-4984-8550-95e7fbca7253
//        5048b670-f9e8-426d-a9d5-b68cf378a045
//        efa190d8-5316-40af-b7f4-c43edfba6074
    }
}

UUID优缺点

优点:

  • • 使用比较简单,自带的工具类生成即可。

  • • 性能好

缺点:

  • • 由于主键生成的不规律性,一般主键都是自动创建索引,长度比较长,带来的问题是占用的存储空间比较大,查询效率不高。

  • • 不适合作为分布式id。

2 自增ID

此处以mysql为例进行使用。

每次进行数据库插入操作的时候,id会进行自动的根据设置的规则进行增加。

可以看到我们数据库遍历的设置。

mysql> show variables like '%increment%';
+-----------------------------+-------+
| Variable_name               | Value |
+-----------------------------+-------+
| auto_increment_increment    | 1     |
| auto_increment_offset       | 1     |
| div_precision_increment     | 4     |
| innodb_autoextend_increment | 64    |
+-----------------------------+-------+

执行插入之后查询下最新的id

INSERT INTO person(username, age) VALUES('elite', 22);
SELECT LAST_INSERT_ID();

自增的ID只是单机数据库下使用,分库分表的情况下不适合,会产生重复的ID。

3.redis

基于全局唯一ID的特性,我们可以通过Redis的INCR命令来生成全局唯一ID。

INCR key:对存储在指定key的数值执行原子的加1操作。如果指定的key不存在,那么在执行incr操作之前,会先将它的值设定为0。

如果指定的key中存储的值不是字符串类型(fix:)或者存储的字符串类型不能表示为一个整数,

@Autowired
    private StringRedisTemplate redisTemplate;
    ///设置一个起始的时间戳
    private static final long BEGIN_TIMESTAMP = xxx;

    /**
     * 生成分布式ID
     * 符号位    时间戳[31位]  自增序号【32位】
     * @param bizType 设置没个业务的
     * @return
     */
    public long nextId(String bizType){
        // 1.生成时间戳
        LocalDateTime now = LocalDateTime.now();
        // 格林威治时间差
        long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
        // 我们需要获取的 时间戳 信息
        long timestamp = nowSecond - BEGIN_TIMESTAMP;
        // 2.生成序号 --》 从Redis中获取
        // 当前当前的日期
        String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
        // 获取对应的自增的序号
        Long increment = redisTemplate.opsForValue().increment("id:" + bizType + ":" + date);
        return timestamp << 32 | increment;
    }

4.雪花算法

雪花算法(Snowflake)是Twitter开源的一种分布式ID生成算法,用来保证在大规模分布式系统中生成全局唯一的ID。雪花算法产生的ID是一个64位的整数,由以下几部分组成:

  • • 符号位:第一位为0。

  • • 时间戳:41位,用来记录时间戳,精确到毫秒。

  • • 数据中心ID:5位,用来记录生成ID的数据中心或机器ID。

  • • 工作机器ID:5位,用来记录在当前数据中心的工作机器ID。

  • • 序列号:12位,用来记录同一毫秒内产生的不同ID。

其主要优点是保证了全局ID的唯一性,避免了分布式系统环境下的ID冲突;并且生成ID的过程中无需依赖数据库等外部系统,减少了系统复杂性。不足的地方是,由于依赖时间戳,如果系统时间回拨,有可能会导致ID冲突。

下面给出Java实现雪花算法的一种方式:

package com.elite.java.distributeid;
public class SnowflakeIdWorker {
    private long workerId;
    private long datacenterId;
    private long sequence = 0L;

    private long twepoch = 1288834974657L;

    private long workerIdBits = 5L;
    private long datacenterIdBits = 5L;
    private long maxWorkerId = -1L ^ (-1L << workerIdBits);
    private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
    private long sequenceBits = 12L;

    private long workerIdShift = sequenceBits;
    private long datacenterIdShift = sequenceBits + workerIdBits;
    private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
    private long sequenceMask = -1L ^ (-1L << sequenceBits);

    private long lastTimestamp = -1L;

    public SnowflakeIdWorker(long workerId, long datacenterId) {
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException("worker Id can't be greater than %d or less than 0");
        }
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException("datacenter Id can't be greater than %d or less than 0");
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    public synchronized long nextId() {
        long timestamp = timeGen();

        if (timestamp < lastTimestamp) {
            throw new RuntimeException("Clock moved backwards. Refusing to generate id for "+ (lastTimestamp - timestamp) + " milliseconds");
        }

        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }

        lastTimestamp = timestamp;

        return ((timestamp - twepoch) << timestampLeftShift) |
        (datacenterId << datacenterIdShift) |
                (workerId << workerIdShift) |
                sequence;
    }

    protected long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    protected long timeGen(){
        return System.currentTimeMillis();
    }

}

测试

public class SnowFlakeTest {

    public static void main(String[] args) {
        SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);
        for (int i = 0; i < 10; i++) {
            long id = idWorker.nextId();
            System.out.println(id);
        }
    }
}

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐