在数据库设计时,表记录唯一ID应该如何设置?

零 Mysql教程评论118字数 4265阅读14分13秒阅读模式

在数据库设计时,表记录唯一ID应该如何设置?

自增ID

在数据库设计中,使用 自增ID(auto-increment ID) 作为记录的唯一ID是一种常见且简便的方法。

优点:文章源自灵鲨社区-https://www.0s52.com/bcjc/mysqljc/16659.html

自增ID 的实现和使用非常简单,数据库管理系统(如MySQL、PostgreSQL)都提供了内置支持,无需手动生成ID,数据库会自动维护和生成唯一ID。文章源自灵鲨社区-https://www.0s52.com/bcjc/mysqljc/16659.html

而且,自增ID 的插入性能通常都比较好,因为自增ID 都是顺序生成的,插入操作通常只需要在表的末尾进行。文章源自灵鲨社区-https://www.0s52.com/bcjc/mysqljc/16659.html

但是,凡事都有但是,自增ID也存在非常难忽视的缺点:文章源自灵鲨社区-https://www.0s52.com/bcjc/mysqljc/16659.html

缺点:文章源自灵鲨社区-https://www.0s52.com/bcjc/mysqljc/16659.html

在 分布式数据库或分库分表 时,使用自增ID可能会导致冲突或需要复杂的协调机制。文章源自灵鲨社区-https://www.0s52.com/bcjc/mysqljc/16659.html

并且在 安全性 方面存在隐患,由于自增ID是可预测的,可能会暴露记录数量或者创建顺序,对安全性敏感的应用可能不适合使用。文章源自灵鲨社区-https://www.0s52.com/bcjc/mysqljc/16659.html

还有就是在后续多个数据库实例之间迁移或合并数据时,可能会遇到ID冲突的问题。文章源自灵鲨社区-https://www.0s52.com/bcjc/mysqljc/16659.html

针对上面提到的问题,可以选择下面几种替代方案。文章源自灵鲨社区-https://www.0s52.com/bcjc/mysqljc/16659.html

替代方案

1. UUID(Universally Unique Identifier)

UUID 是一种通用唯一识别码,具有 128 位长度,几乎 不会发生冲突。文章源自灵鲨社区-https://www.0s52.com/bcjc/mysqljc/16659.html

优点:

  • 全球唯一性:适合分布式系统,不会发生冲突。
  • 安全性:难以预测,不容易暴露数据量和顺序。

缺点:

  • 性能:UUID 的长度较长,索引和存储开销较大,查询性能可能不如自增ID。
  • 可读性:不如自增 ID 直观,调试和管理不便。

示例代码:

java

复制代码
import java.util.UUID;

public class User {
    private UUID id;
    private String name;
    
    public User() {
        this.id = UUID.randomUUID();
    }
    
    // getters and setters
}

2. 雪花算法(Snowflake ID)

雪花算法(Snowflake Algorithm) 由 Twitter 开发,用于生成分布式系统中的全局唯一ID。 其生成的 ID 具有时间顺序性,且可以在多个节点上高效生成。

优点:

  • 有序性:生成的 ID 是有序的,适合大规模分布式系统。
  • 高性能:生成ID速度快,适合高并发场景。

缺点:

  • 复杂性:实现较复杂,需要配置机器ID和时间戳等参数。

雪花算法生成的 ID是一个 64 位的整数,结构如下:

shell

复制代码
0 - 41位时间戳 - 10位节点ID - 12位序列号
  • 1 位符号位:始终位 0, 因为生成的 ID 是正数;
  • 41 位时间戳:表示当前时间,相当于一个起始时间(通常为算法开始时间)的偏移量,可以表示约 69 年的时间。
  • 10 位节点ID:表示机器或数据中心的 ID, 可以支持最多 1024 个节点。
  • 12 位序列号:在同一毫秒内生成的序列号,可以支持每个节点每毫秒生成 4096 个唯一 ID。

工作原理:

  1. 时间戳:获取当前时间戳(相对于设定的起始时间)
  2. 节点ID:获取当前节点的 ID(通常是机器ID或数据中心ID)
  3. 序列号:在同一毫秒内,如果有多个ID请求,则通过序列号来区分这些 ID,序列号在每毫秒内递增。如果序列号超过 4095,则等待下一毫秒。

机器ID workId 和 数据中心ID datacenterId 用于标识生成唯一 ID 的节点,这些节点可以是物理服务器、虚拟机器或数据中。

这两个 ID 一般在 application.propeties 或 application.yml 等配置文件中进行定义。

application.properti...

复制代码
# application.properties
snowflake.workerId=1
snowflake.datacenterId=1

在 Spring 中进行配置注入:

java

复制代码
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SnowflakeConfig {

    @Value("${snowflake.workerId}")
    private long workerId;

    @Value("${snowflake.datacenterId}")
    private long datacenterId;

    public long getWorkerId() {
        return workerId;
    }

    public long getDatacenterId() {
        return datacenterId;
    }
}

实现雪花算法:

以下是一个 java 实现的示例:

java

复制代码
public class SnowflakeIdGenerator {

    // 起始时间戳(可以设置为某个固定的时间点,如2020-01-01)
    private final long twepoch = 1577836800000L;

    // 机器ID所占的位数
    private final long workerIdBits = 5L;

    // 数据中心ID所占的位数
    private final long datacenterIdBits = 5L;

    // 支持的最大机器ID
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);

    // 支持的最大数据中心ID
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);

    // 序列在ID中占的位数
    private final long sequenceBits = 12L;

    // 机器ID左移位数
    private final long workerIdShift = sequenceBits;

    // 数据中心ID左移位数
    private final long datacenterIdShift = sequenceBits + workerIdBits;

    // 时间戳左移位数
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;

    // 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095)
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);

    private long workerId;
    private long datacenterId;
    private long sequence = 0L;
    private long lastTimestamp = -1L;

    public SnowflakeIdGenerator(long workerId, long datacenterId) {
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format("workerId can't be greater than %d or less than 0", maxWorkerId));
        }
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException(String.format("datacenterId can't be greater than %d or less than 0", maxDatacenterId));
        }
        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 static void main(String[] args) {
        SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator(1, 1);
        for (int i = 0; i < 10; i++) {
            long id = idGenerator.nextId();
            System.out.println(id);
        }
    }
}

使用示例:

在实际使用中,可以实例化 SnowflakeIdGenerator, 并调用 nextId() 方法生成唯一 ID。

java

复制代码
public class IdService {
    private final SnowflakeIdGenerator idGenerator;

    public IdService() {
        // 初始化时指定机器ID和数据中心ID
        this.idGenerator = new SnowflakeIdGenerator(1, 1);
    }

    public long generateId() {
        return idGenerator.nextId();
    }
}

数据库存储:

在 MySQL 中,常用的字段类型是 BIGINT , 因为它能够存储 64 位的整数。

sql

复制代码
CREATE TABLE example (
    id BIGINT NOT NULL PRIMARY KEY,
    name VARCHAR(255) NOT NULL
);

零
  • 转载请务必保留本文链接:https://www.0s52.com/bcjc/mysqljc/16659.html
    本社区资源仅供用于学习和交流,请勿用于商业用途
    未经允许不得进行转载/复制/分享

发表评论