数据库主键(id)的生成方案在设计系统时非常关键,既影响性能,也影响分布式扩展能力。
下面给出 5 种主流方案,并结合 Spring Boot + MyBatis + MySQL 项目,推荐最佳实践及示例代码。
🧩 一、常见主键生成方案对比
| 方案类型 | 示例 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 1. 自增主键(AUTO_INCREMENT) | id BIGINT AUTO_INCREMENT |
简单、易用、顺序性好 | 分布式环境易冲突、插入热点 | 单节点系统、小型项目 |
| 2. UUID(字符串) | id CHAR(36) |
全局唯一 | 查询慢、存储冗余大 | 无需排序的全局唯一标识 |
| 3. 雪花算法(Snowflake ID) | long id = Snowflake.nextId(); |
高性能、趋势递增 | 实现复杂、时间依赖 | 分布式系统 |
| 4. Redis / 数据库序列号 | 从 Redis 或 DB 获取自增号 | 全局唯一、可控 | 有外部依赖 | 多系统共享ID |
| 5. 自定义混合ID(时间戳 + 随机数) | 例如 20251025142312345 |
可读性好 | 可能重复(需控制) | 临时或日志型记录 |
✅ 二、推荐方案(Spring Boot + MyBatis + MySQL)
建议使用:
雪花算法(Snowflake ID) + MyBatis 自动生成主键
这样既:
- 不依赖 MySQL 自增(可分库分表);
- 不会发生 ID 冲突;
- 效率极高;
- ID 可作为时间序列排序。
✅ 三、实现方案(雪花算法版本)
1️⃣ 新建工具类 SnowflakeIdGenerator.java
java
package com.example.qrcode.util;
/**
* 雪花算法ID生成器
* 64位ID结构如下:
* 1bit 符号位 + 41bit 时间戳 + 10bit 机器ID + 12bit 序列号
*/
public class SnowflakeIdGenerator {
// 起始时间戳(自定义纪元)
private static final long START_STAMP = 1672531200000L; // 2023-01-01
// 每部分占位
private static final long SEQUENCE_BIT = 12; // 序列号占12位
private static final long MACHINE_BIT = 10; // 机器ID占10位
// 最大值
private static final long MAX_MACHINE_NUM = ~(-1L << MACHINE_BIT);
private static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);
// 偏移量
private static final long MACHINE_LEFT = SEQUENCE_BIT;
private static final long TIMESTAMP_LEFT = SEQUENCE_BIT + MACHINE_BIT;
private long machineId; // 机器ID
private long sequence = 0L;
private long lastStamp = -1L;
public SnowflakeIdGenerator(long machineId) {
if (machineId > MAX_MACHINE_NUM || machineId < 0) {
throw new IllegalArgumentException("机器ID超出范围");
}
this.machineId = machineId;
}
// 获取下一个ID
public synchronized long nextId() {
long currStamp = System.currentTimeMillis();
if (currStamp < lastStamp) {
throw new RuntimeException("时钟回拨,拒绝生成ID");
}
if (currStamp == lastStamp) {
sequence = (sequence + 1) & MAX_SEQUENCE;
if (sequence == 0L) {
currStamp = waitNextMillis(currStamp);
}
} else {
sequence = 0L;
}
lastStamp = currStamp;
return (currStamp - START_STAMP) << TIMESTAMP_LEFT // 时间部分
| (machineId << MACHINE_LEFT) // 机器部分
| sequence; // 序列部分
}
private long waitNextMillis(long currStamp) {
while (currStamp <= lastStamp) {
currStamp = System.currentTimeMillis();
}
return currStamp;
}
}
2️⃣ 在 Service 中使用
ini
package com.example.qrcode.service;
import com.example.qrcode.entity.QrCodeRecord;
import com.example.qrcode.mapper.QrCodeMapper;
import com.example.qrcode.util.QrCodeUtil;
import com.example.qrcode.util.SnowflakeIdGenerator;
import org.springframework.stereotype.Service;
@Service
public class QrCodeService {
private final QrCodeMapper qrCodeMapper;
private final SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator(1L);
public QrCodeService(QrCodeMapper qrCodeMapper) {
this.qrCodeMapper = qrCodeMapper;
}
public QrCodeRecord createAndSave(String content) throws Exception {
byte[] imageBytes = QrCodeUtil.generateQrAsBytes(content, 300, 300);
QrCodeRecord record = new QrCodeRecord();
record.setId(idGenerator.nextId());
record.setContent(content);
record.setImage(imageBytes);
qrCodeMapper.insert(record);
return record;
}
}
3️⃣ 数据库表中改为手动ID插入
sql
CREATE TABLE qr_code_record (
id BIGINT PRIMARY KEY COMMENT '雪花算法ID',
content VARCHAR(500) NOT NULL,
image LONGBLOB NOT NULL,
created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
✅ 四、其他备选方案(简略)
🚀 1. MySQL 自增主键
sql
id BIGINT PRIMARY KEY AUTO_INCREMENT
简单直接,适合单机。
🚀 2. UUID 方案
less
record.setId(UUID.randomUUID().toString());
表结构:
scss
id CHAR(36) PRIMARY KEY
但效率不如 Long 型。
🚀 3. Redis 全局序列号
ini
Long id = redisTemplate.opsForValue().increment("global:qr:id");
✅ 五、推荐结论
| 场景 | 推荐方案 |
|---|---|
| 单体应用、小项目 | AUTO_INCREMENT |
| 分布式系统、微服务 | ✅ 雪花算法 (Snowflake) |
| 跨系统全局唯一ID | Redis 序列 或 UUID |
| 临时或可读性强 ID | 时间戳 + 随机数拼接 |