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

Spring Boot重启后PostgreSQL自增ID跳号问题咨询

问题描述

我使用Spring Boot结合JPA与PostgreSQL开发应用,发现每次重启服务器后,表中记录的自增ID会跳过50个值。例如,表中最后一条记录ID为50,重启后通过API插入新记录时,ID会被设为100;若不重启服务器,插入记录的ID则会按101、102正常递增。

我的实体类继承自BaseEntity基类,该类通过@GeneratedValue(strategy = GenerationType.AUTO)生成ID,Hibernate DDL配置为仅校验模式。

请问:

  • 能否实现服务器重启后自增ID从最后已知ID开始,避免跳号?
  • 该问题是否与PostgreSQL的序列机制相关?

BaseEntity代码

@Getter
@Setter
@MappedSuperclass
public abstract class BaseEntity implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id", nullable = false)
    private Integer id;
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof BaseEntity that)) return false;
        return id.equals(that.id);
    }
    @Override
    public int hashCode() {
        return Objects.hash(id);
    }
    @Override
    public String toString() {
        return "BaseEntity {" +
                "id = " + id +
                "}";
    }
}

BaseEntityAudit代码

@Getter
@Setter
@MappedSuperclass
public abstract class BaseEntityAudit extends BaseEntity implements Serializable {
    private String createdBy;
    private String updatedBy;
    @CreationTimestamp
    @Column(name = "created_at", updatable = false)
    private Date createdAt;
    @UpdateTimestamp
    @Column(name = "updated_at")
    private Date updatedAt;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof BaseEntityAudit)) return false;
        if (!super.equals(o)) return false;
        BaseEntityAudit that = (BaseEntityAudit) o;
        return createdBy.equals(that.createdBy) &&
                updatedBy.equals(that.updatedBy) &&
                createdAt.equals(that.createdAt) &&
                updatedAt.equals(that.updatedAt);
    }

    @Override
    public int hashCode() {
        return Objects.hash(super.hashCode(),
                createdBy, updatedBy, createdAt, updatedAt);
    }

    @Override
    public String toString() {
        return "BaseEntityAudit{" +
                "createdBy='" + createdBy + '\'' +
                ", updatedBy='" + updatedBy + '\'' +
                ", createdAt=" + createdAt +
                ", updatedAt=" + updatedAt +
                "}" + super.toString();
    }
}
问题原因与解决方案

问题根源

这个问题完全和PostgreSQL的序列机制以及Hibernate的默认配置相关。

当你使用GenerationType.AUTO时,Hibernate在PostgreSQL环境下会默认采用序列(Sequence)生成ID。PostgreSQL的序列默认开启了缓存(cache)功能,默认缓存大小为50——Hibernate会一次性从数据库获取50个ID存入内存,后续插入操作直接使用内存中的ID,避免频繁查询数据库,以此提升性能。

但服务器重启时,内存中未使用的缓存ID会全部丢失。重启后Hibernate会再次向序列请求下一批50个ID,这就导致了ID跳号:比如之前已使用50个ID,缓存中剩余的50个未被使用就丢失,重启后序列直接从100开始分配ID。

实现无跳号的解决方案

方案1:修改现有序列的缓存大小为1

直接在PostgreSQL中修改对应表的序列,将缓存设为1:

ALTER SEQUENCE 你的表名_id_seq CACHE 1;

此配置下,Hibernate每次只能从数据库获取1个ID,重启后不会丢失未使用的ID,但会牺牲部分性能——每次插入都需要查询数据库获取ID。

方案2:在实体类中显式配置序列缓存

在BaseEntity的ID字段上,通过@SequenceGenerator显式指定序列并设置缓存大小为1:

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "base_seq")
@SequenceGenerator(name = "base_seq", sequenceName = "base_id_seq", allocationSize = 1)
@Column(name = "id", nullable = false)
private Integer id;

这里的allocationSize对应PostgreSQL序列的缓存大小,设为1即可避免缓存丢失问题。注意如果数据库中已存在对应序列,需要确保序列的缓存配置也改为1,否则代码配置可能不生效。

方案3:改用IDENTITY生成策略(不推荐高并发场景)

如果你的PostgreSQL版本支持,可以切换到GenerationType.IDENTITY策略,该策略依赖数据库的自增列(如PostgreSQL的SERIAL或BIGSERIAL),不会有缓存相关的跳号问题:

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Integer id;

但此方式在高并发场景下性能较差,因为每次插入都需要等待数据库生成ID,无法批量获取ID值。

额外说明

  • 多数业务场景下,ID跳号并不会影响功能,反而序列缓存带来的性能提升更有价值。仅当业务对ID连续性有严格要求(如发票编号、流水号)时,才需要调整缓存配置。
  • 将缓存设为1后,需注意高并发插入场景下的性能损耗,需根据业务实际情况权衡选择。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 15:58:14