数据库id生成方案

数据库主键(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 时间戳 + 随机数拼接
相关推荐
Pniubi1 小时前
MySQL靠什么实现事务的?事务隔离级别有哪些?
java·数据库·mysql
一条Go1 小时前
OnCall 时段路由:跨午夜、左闭右开与「Local 不是时区」
后端·架构·github
Moment2 小时前
不会后期的我,发现了一个开源胶片神器😍😍😍
前端·后端·开源
数据库小学妹2 小时前
pgbouncer 事务池:prepared statement、临时表与会话状态
数据库·后端·postgresql
aiks_yuan2 小时前
MySQL 死锁怎么排查:看懂 INNODB STATUS 死锁日志,先查后插为什么必死锁(MySQL 8.2 实测)
mysql
三小河2 小时前
SWC 凭什么比 Babel 快 ?项目优化迁移踩坑记录
前端·后端
鶴哥只手遮天3 小时前
《Redis 8.10.3 深度剖析》系列 ·第 17 章. 一个迟到十六年的类型
redis·后端·开源
知守观3 小时前
同一个团队两套分包:DDD 四层落地后,domain 层最大的类还是 5885 行
java·后端·领域驱动设计
Anymous3 小时前
我手写的 Redis 锁,输给了 10 秒的 TTL
后端·架构
Rain的Java大神之路4 小时前
设计一套生产级 API 接口:从契约、安全、幂等到限流,我踩过的坑都在这了
java·后端·面试