自增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。
工作原理:
- 时间戳:获取当前时间戳(相对于设定的起始时间)
- 节点ID:获取当前节点的 ID(通常是机器ID或数据中心ID)
- 序列号:在同一毫秒内,如果有多个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
);
评论