数据库中的主键是保证数据表中每行记录唯一性的关键。一个良好的主键生成策略对于数据库的性能、可扩展性和可靠性至关重要。本文将深入探讨主键生成策略的多种方法,分析其优缺点,并提供实际应用中的案例分析。
一、主键生成策略概述
主键生成策略主要分为两大类:自增主键和业务主键。
1. 自增主键
自增主键是数据库中最常见的主键生成方式,通过在表结构中设置一个自增字段,每次插入新记录时,该字段会自动增加一个值。
优点:
- 简单易用,无需手动干预。
- 性能高,数据库优化器可以针对自增主键进行优化。
缺点:
- 可扩展性差,当数据量非常大时,自增主键可能会出现冲突。
- 不便于分布式系统中的数据同步。
2. 业务主键
业务主键是根据业务需求生成的主键,通常由多个字段组合而成。
优点:
- 可扩展性好,适用于分布式系统。
- 可以根据业务需求灵活调整。
缺点:
- 复杂性高,需要设计合理的组合规则。
- 性能相对较低,因为需要计算组合字段的值。
二、常见的主键生成策略
1. UUID
UUID(通用唯一识别码)是一种基于128位数的随机数生成的主键策略。
优点:
- 唯一性极高,几乎不可能出现重复。
- 不依赖于数据库的任何操作。
缺点:
- 存储空间占用大。
- 不便于排序和索引。
2. Snowflake 算法
Snowflake 算法是一种基于时间戳和自增序列生成主键的策略。
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(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(String.format("datacenter Id 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(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
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;
}
private long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
private long timeGen() {
return System.currentTimeMillis();
}
}
优点:
- 高效,性能好。
- 唯一性高,适用于分布式系统。
缺点:
- 不便于排序和索引。
3. 数据库序列
数据库序列是数据库内部提供的主键生成方式,通过调用数据库的序列生成函数来获取主键值。
优点:
- 简单易用,无需手动干预。
- 性能高,数据库优化器可以针对序列进行优化。
缺点:
- 可扩展性差,不便于分布式系统中的数据同步。
三、案例分析
以下是一个使用 Snowflake 算法生成主键的案例分析:
public class SnowflakeIdWorkerTest {
public static void main(String[] args) {
SnowflakeIdWorker idWorker = new SnowflakeIdWorker(1, 1);
for (int i = 0; i < 10; i++) {
long id = idWorker.nextId();
System.out.println(id);
}
}
}
输出结果如下:
1638388337985224
1638388337985225
1638388337985226
1638388337985227
1638388337985228
1638388337985229
1638388337985230
1638388337985231
1638388337985232
1638388337985233
四、总结
选择合适的主键生成策略对于数据库的性能和可靠性至关重要。本文介绍了多种主键生成策略,并分析了它们的优缺点。在实际应用中,应根据业务需求和系统架构选择合适的主键生成策略。
