You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Java Spring+Oracle数据库环境下生成无间隙的主键ID

Oracle 多并发场景下无间隙主键实现方案

无间隙主键的核心要求是ID分配必须和业务事务强绑定,因此无法使用Oracle原生序列(序列生成与事务生命周期 decouple,回滚必然产生间隙),以下是两种生产环境可落地的方案,按适用场景区分:

方案一:专用序号控制表 + 行级锁(实时获取ID,适用TPS≤1000的场景)

这是满足硬性无间隙要求的最通用方案,通过事务级锁保证ID分配和业务操作同生共死:

  1. 新建序号控制表,存储各业务线的当前序号值
CREATE TABLE sequence_generator (
    seq_name VARCHAR2(50) PRIMARY KEY,
    current_value NUMBER(18) NOT NULL,
    step NUMBER(5) DEFAULT 1 NOT NULL
);
-- 初始化业务对应的序列
INSERT INTO sequence_generator(seq_name, current_value, step) VALUES ('NUM_SEQ', 0, 1);
COMMIT;
  1. 编写ID获取函数,通过行级排他锁保证同一时间只有一个事务能拿对应序列的ID
CREATE OR REPLACE FUNCTION get_next_seq(p_seq_name VARCHAR2) RETURN NUMBER IS
    v_next_val NUMBER;
BEGIN
    -- 行级锁会持有到当前事务提交/回滚,其他事务拿同序列ID会阻塞等待
    SELECT current_value + step INTO v_next_val
    FROM sequence_generator
    WHERE seq_name = p_seq_name
    FOR UPDATE;

    UPDATE sequence_generator
    SET current_value = v_next_val
    WHERE seq_name = p_seq_name;

    RETURN v_next_val;
END;
/
  1. Spring 集成方式
    在业务事务开启后第一时间调用函数获取ID,再执行后续业务逻辑:
@Repository
public interface SeqRepository extends JpaRepository<SequenceGenerator, String> {
    @Query(value = "SELECT get_next_seq('NUM_SEQ') FROM DUAL", nativeQuery = true)
    Long getNextNumSeq();
}

如果事务回滚,序号表的更新会同步回滚,不会产生间隙;不同业务线的序列互相独立,不会互相阻塞。

方案二:异步分配序号(高并发场景,允许ID延迟获取)

如果业务允许写入时不立即拿到无间隙序号,仅要求最终序号无间隙,可以用该方案获得极高的并发性能:

  • 业务表先用普通Oracle序列作为主键(开启缓存提升写入性能),额外新增biz_seq字段存储无间隙序号,默认值为NULL
  • 启动单线程后台任务,按主键/创建时间的升序,给biz_seq为NULL的记录依次分配连续序号
    单线程分配完全不存在并发冲突,也不会有间隙,业务写入逻辑完全不需要加锁,适合高并发写入、序号仅用于事后展示的场景(比如账单编号、工单编号等)

避坑提醒

  • 不要尝试用max(id)+1加全表锁的方式实现,锁粒度远大于序号表的行级锁,并发性能会差几个数量级
  • 不要在原生序列上浪费时间,无论怎么调整参数都无法解决事务回滚导致的间隙问题

内容的提问来源于stack exchange,提问作者WattMonki

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.23 22:06:03